> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# أخطاء شائعة وأسلوب Swift الحديث Idiomatic Swift
- URL: https://arabroot.io/articles/اخطاء-شائعة-واسلوب-swift-الحديث/
- Published: 2026-10-31T10:01:49.000Z
- Updated: 2026-10-31T11:01:14.000Z
- Description: شغلنا خمسة عشر خطأ يأتي بها المطورون من Java وPython وTypeScript إلى Swift، كل خطأ بمخرجه الحقيقي وإصلاحه، ثم خطة قراءة مرتبة من المصادر الرسمية.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, لغات البرمجة عملياً

بعد أن صارت مكتبة PriceKit وأداة المحاسبة وخدمة الطلبات في متجر التمور والقهوة مكتوبة بـ Swift، انضم إلى الفريق مطوران جديدان: الأول قضى سنوات في Java، والثاني يكتب Python وTypeScript. وكلاهما مطور جيد، ولكن طلبات الدمج Pull Requests الأولى منهما كانت متشابهة بطريقة لافتة: علامة تعجب بعد كل قيمة اختيارية، وكلاسات في كل مكان، وسعر بالريال من نوع `Double`، وحلقة تعدل نسخة من المنتج وتظن أنها عدلت المنتج نفسه، وفاتورة تطبع للعميل «Optional("0500000000")». ولا شيء من هذا خطأ في البرمجة بالمعنى العام، وإنما عادات صحيحة في لغة أخرى نقلت كما هي إلى Swift.

وقد يتساءل البعض: لماذا تتكرر نفس الأخطاء من مطورين مختلفين؟ والإجابة أن كل لغة تعلمك افتراضات لا تراها، فمطور Java تعلم أن كل كائن مرجع، وأن `null` يمكن أن يأتي من أي مكان فيفحصه عندما يتذكر، وأن الأرقام تتحول بين الأنواع وحدها، ومطور Python تعلم أن الأخطاء استثناءات تصعد وحدها، وأن النص يفهرس برقم، ومطور JavaScript تعلم أن الانتظار لا يحجب أحداً لأن هناك خيطاً واحداً. وSwift غيرت هذه الافتراضات كلها عمداً، ووضعت أغلب التغيير في نظام الأنواع، فعندما تنقل عادتك القديمة إما أن يرفضها المترجم، وهذا أفضل الاحتمالات، أو أن تجد طريقة لتسكته، وهنا يبدأ الخطأ الحقيقي.

وهذا المقال هو الأخير في مرحلة [لغة Swift للمطورين](https://arabroot.io/start/#stage-lang-swift)، وقد جمعنا فيه خمسة عشر خطأ من هذا النوع، وكل خطأ له ملف خاطئ نترجمه ونشغله ونعرض مخرجه الحقيقي، ثم ملف صحيح بالأسلوب الذي تتوقعه Swift ومخرجه. وبعضها يمسكه المترجم قبل أن يعمل الكود، وهذا هو الجيد، وبعضها يوقف البرنامج، وأخطرها ما يعمل بهدوء ويعطي نتيجة خاطئة. وفي آخر المقال خطة قراءة من المصادر الرسمية لمن يريد أن يكمل بعد هذه المرحلة.

وسوف نناقش في هذا المقال ما يلي:

- طريقة تشغيل الأخطاء، ولماذا بوضع التصحيح Debug.
- أخطاء الاختياري Optional: القوة بـ `!`، والاختياري داخل النص، و`try?` التي تخفي السبب، والاختياري الضمني `Int!`.
- أخطاء الأرقام والنصوص: خلط `Int` و`Double`، والمال بـ `Double`، وفهرسة النص برقم، وطول النص بالحروف أم بالبايتات.
- أخطاء القيم والمراجع Value Semantics: الحلقة التي تعدل نسخة، والكلاس المشترك، والإغلاق الذي يمسك `self`.
- أخطاء البروتوكولات والتعداد: الدالة في الامتداد التي لا تستدعى، و`default` التي تبتلع الحالة الجديدة.
- أخطاء التزامن Concurrency: الانتظار الحاجب داخل `async`، والكلاس المشترك بين المهام.
- مراجعة طلب دمج كامل قبل وبعد، وقواعد swift-format التي تمسك بعض الأخطاء، وملخص الأسلوب الحديث في Swift، وخطة قراءة من المصادر الرسمية مرتبة.

## كيف شغلنا الأخطاء؟ Lab

كل خطأ في مجلد `lab/m` ملفان أو ثلاثة: `NN-wrong.swift` و`NN-right.swift`، وسكربت واحد يترجم كل ملف بوضع Swift 6 ثم يشغله إذا ترجم، ويحفظ رمز الخروج:

```bash
for f in $(ls *.swift | sort); do
  echo "===== $f"
  swiftc -swift-version 6 "$f" -o /tmp/prog 2>&1 | grep -E "error|warning|note" | head -6
  if [ -x /tmp/prog ]; then timeout 20 /tmp/prog > /tmp/out 2>&1; code=$?; head -4 /tmp/out; echo "exit=$code"; rm -f /tmp/prog
  else echo "(did not compile)"; fi
done
```

```bash
docker run --rm --cpus 2 --memory 512m -v "$PWD:/lab" -w /lab/m swift:6.4.2-noble bash ../run.sh
```

واخترنا الترجمة بدون `-O`، أي وضع التصحيح الذي يعمل به المطور طوال يومه، والسبب أننا رأينا في [مقال الأنواع والذاكرة](https://arabroot.io/articles/%D8%A7%D9%84%D8%A7%D9%86%D9%88%D8%A7%D8%B9-%D9%88%D8%A7%D9%84%D8%B0%D8%A7%D9%83%D8%B1%D8%A9-arc-%D9%88%D8%A7%D9%84%D8%A7%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-swift/) أن وضع الإنتاج يحذف نصوص رسائل التوقف، وفي التشغيل الأول بـ `-O` خرج الخطأ الأول برمز 141 بدل 132، لأن السكربت كان يمرر المخرج إلى `head` مباشرة فوصلت إشارة الأنبوب المغلق `SIGPIPE` قبل أن نقرأ رمز التوقف الحقيقي، فصار السكربت يحفظ المخرج في ملف أولاً ثم يقرؤه. والخريطة التالية تجمع الأخطاء الخمسة عشر بحسب من يمسكها:

![خريطة الأخطاء الخمسة عشر في أربعة أعمدة: ما يرفضه المترجم بخطأ ترجمة وهو خلط Int وDouble وفهرسة النص برقم وThread.sleep داخل async والكلاس المشترك بين المهام، وما يحذر منه فقط وهو الاختياري داخل النص، وما يوقف البرنامج وهو القوة ! على nil والاختياري الضمني Int!، وما يعطي نتيجة خاطئة بصمت وهو try? والمال بـ Double وطول النص بالحروف والحلقة على نسخة والكلاس المشترك والإغلاق الذي يمسك self والدالة في الامتداد وdefault التي تبتلع الحالة الجديدة، ومعها usleep داخل async](https://arabroot.io/content/images/2026/10/swift-idioms-01-mistake-map.webp)

أخطر الأخطاء ليست التي يرفضها المترجم، وإنما التي تعمل بهدوء

## أخطاء الاختياري Optional

### 1\. علامة التعجب ! على قيمة قد تكون فارغة Force Unwrap

القادم من Java اعتاد أن القاموس `Map.get` يرجع `null` ثم يفحص أو لا يفحص، وفي Swift يرجع القاموس اختيارياً، فيكتب المطور `!` ليتخلص من الاختياري بسرعة:

```swift
let stock = ["تمر": 10, "قهوة": 0]
let wanted = "هيل"
let left = stock[wanted]!   // crashes: no such key
print("left:", left)
```

```text
prog/01-wrong.swift:3: Fatal error: Unexpectedly found nil while unwrapping an Optional value
*** Signal 4: Backtracing from 0x7f1aa33b61ef... done ***
exit=132
```

والأسلوب الصحيح أن تقول ماذا تريد عندما تغيب القيمة: فرع آخر بـ `if let`، أو خروج مبكر بـ `guard let`، أو قيمة افتراضية، والقاموس نفسه يقبل قيمة افتراضية في الفهرس:

```swift
if let left = stock[wanted] { print("left:", left) } else { print("unknown product:", wanted) }
print("with default:", stock[wanted, default: 0])
```

```text
unknown product: هيل
with default: 0
```

وقاعدة عملية في المراجعة: كل `!` في كود الإنتاج يحتاج تعليقاً يشرح لماذا لا يمكن أن تكون القيمة فارغة، فإذا لم تستطع أن تكتب هذا التعليق فلا تكتب `!`. والاستثناءات المقبولة قليلة، مثل رابط ثابت مكتوب في الكود `URL(string: "https://...")!`، لأن فشله خطأ مبرمج يظهر في أول تشغيل.

ولماذا صممت Swift الاختياري بهذه الصرامة؟ لأن القيمة الفارغة التي يمكن أن تكون في أي مرجع هي ما سماه Tony Hoare، الذي أدخلها في لغة ALGOL W عام 1965، [خطأ المليار دولار](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/?ref=arabroot.io)، لكثرة ما سببته من أعطال وثغرات. وSwift حلتها بأن جعلت الغياب جزءاً من النوع، فالمترجم يعرف أين قد تغيب القيمة ويجبرك على قرار في كل مكان. والعلامة `!` هي الباب الذي يعيد المشكلة كما كانت، ولذلك يعاملها المراجعون كإشارة حمراء.

### 2\. الاختياري داخل النص Optional Interpolation

هذا الخطأ رآه العميل نفسه في رسالة SMS:

```swift
struct Customer { let name: String; let phone: String? }
let c = Customer(name: "سارة", phone: "0500000000")
print("SMS to \(c.phone)")
```

```text
09-wrong.swift:3:17: warning: string interpolation produces a debug description for an optional value; did you mean to make this explicit?
  |                 | |- note: provide a default value to avoid this warning
  |                 | `- note: use 'String(describing:)' to silence this warning
SMS to Optional("0500000000")
exit=0
```

المترجم حذر، ولكنه تحذير وليس خطأ، والكود ترجم وعمل وطبع الكلمة `Optional` مع علامات التنصيص. وفي Python أو JavaScript كان سيطبع `None` أو `undefined` إذا غابت القيمة، أما هنا فيطبع غلاف الاختياري حتى عندما تكون القيمة موجودة. والإصلاح أن تفتح الاختياري قبل أن تضعه في النص:

```swift
if let phone = c.phone { print("SMS to \(phone)") } else { print("no phone for \(c.name)") }
```

```text
SMS to 0500000000
```

ولهذا السبب لا تترك التحذيرات تتراكم في المشروع، فهذا التحذير وحده كان سيمنع رسالة غريبة لآلاف العملاء. وإذا أردت أن يفشل البناء عند التحذيرات فاستخدم `-warnings-as-errors` في إعدادات الهدف، أو الوسم `@diagnose` الجديد في Swift 6.4 الذي يتحكم في التحذيرات من داخل الكود.

ونفس المشكلة تظهر في مكانين آخرين على السيرفر: السجلات Logs، حيث يكتب سطر مثل `logger.info("user \(id)")` كلمة `Optional` في كل سطر فيصعب البحث فيها، وردود JSON التي تبنيها بالنص يدوياً بدل `Codable`. أما `Codable` نفسه فيتعامل مع الاختياري بشكل صحيح، فيحذف الحقل أو يكتبه `null` بحسب الإعداد، ولذلك ابن الردود دائماً بـ `Codable` وليس بإقحام النصوص.

### 3\. try? التي تخفي السبب

ملف إعدادات الخدمة بـ JSON، والمطور القادم من Python كتب قراءته في سطر واحد:

```swift
import Foundation
struct Config: Decodable { let port: Int; let vatPercent: Int }
let json = #"{"port": 8080, "vat_percent": 15}"#
let cfg = try? JSONDecoder().decode(Config.self, from: Data(json.utf8))
print("config:", cfg as Any)        // nil, and the reason is gone
```

```text
config: nil
```

النتيجة `nil`، ولا شيء آخر. هل الملف غير موجود؟ هل JSON غير صحيح؟ هل حقل ناقص؟ لا نعرف. والأسلوب الصحيح أن تلتقط الخطأ وتطبعه أو ترميه إلى من يستطيع أن يقرر:

```swift
do {
    let cfg = try JSONDecoder().decode(Config.self, from: Data(json.utf8))
    print(cfg)
} catch {
    print("config error:", error)
    let d = JSONDecoder(); d.keyDecodingStrategy = .convertFromSnakeCase
    print("with convertFromSnakeCase:", try! d.decode(Config.self, from: Data(json.utf8)))
}
```

```text
config error: DecodingError.keyNotFound: Key 'vatPercent' not found in keyed decoding container. Debug description: No value associated with key CodingKeys(stringValue: "vatPercent", intValue: nil) ("vatPercent").
with convertFromSnakeCase: Config(port: 8080, vatPercent: 15)
```

والرسالة تقول السبب بدقة: الحقل في Swift اسمه `vatPercent` وفي JSON اسمه `vat_percent`، و`Codable` يطابق الأسماء حرفياً، والإصلاح سطر واحد هو استراتيجية [convertFromSnakeCase](https://developer.apple.com/documentation/foundation/jsondecoder/keydecodingstrategy-swift.enum/convertfromsnakecase?ref=arabroot.io)، أو مفاتيح `CodingKeys` مكتوبة يدوياً. ولاحظ أن `try!` في السطر الأخير مقبولة هنا فقط لأنها تجربة، وفي كود حقيقي تكون `try` عادية.

ومتى تكون `try?` مناسبة؟ عندما يكون الفشل والغياب شيئاً واحداً فعلاً من وجهة نظر الكود، مثل قراءة قيمة من ذاكرة مؤقتة Cache إذا وجدت، أو تحويل نص إلى رقم من مدخل اختياري لا يهمك لماذا فشل. أما قراءة الإعدادات والاتصال بقاعدة البيانات واستدعاء خدمة الدفع فكلها عمليات يحتاج من يقرأ السجلات بعد منتصف الليل أن يعرف سبب فشلها بالضبط.

### 4\. الاختياري الضمني Implicitly Unwrapped Optional: Int!

من يقرأ كود iOS القديم يرى خصائص مثل `@IBOutlet var label: UILabel!`، فيظن أن `!` بعد النوع طريقة مريحة لقيمة «تضبط لاحقاً»، ثم يستخدمها في خدمة على السيرفر:

```swift
final class OrderService {
    var vatPercent: Int!                 // "set later", like an IBOutlet
    func total(_ subtotal: Int) -> Int { subtotal + (subtotal * vatPercent + 50) / 100 }
}
let service = OrderService()
print(service.total(2970))           // configure() was never called
```

```text
p/15-wrong.swift:3: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
*** Signal 4: Backtracing from 0x7f5f66f9e1ef... done ***
exit=132
```

النوع `Int!` اختياري حقيقي، ولكن المترجم يفتحه بالقوة في كل مكان يستخدم فيه، فهو علامة `!` مخفية في كل سطر يقرأ الخاصية. وفي واجهات iOS له سبب، لأن النظام يربط العناصر بعد إنشاء الكائن وقبل أي استخدام، أما في كودك فهو طريقة لتأجيل سؤال «ما الذي يحتاجه هذا الكائن ليعمل؟». والأسلوب الصحيح أن تجيب على السؤال في دالة البناء:

```swift
final class OrderService {
    let vatPercent: Int
    init(vatPercent: Int) { self.vatPercent = vatPercent }   // no service without its configuration
    func total(_ subtotal: Int) -> Int { subtotal + (subtotal * vatPercent + 50) / 100 }
}
let service = OrderService(vatPercent: 15)
print(service.total(2970))
```

```text
3416
```

والنتيجة 3416 هللة، أي 34.16 ريالاً، وهو نفس الرقم الذي رأيناه في كل مقالات المرحلة. ولا يمكن الآن أن يوجد `OrderService` بدون نسبة الضريبة، فالخطأ الذي كان يظهر وقت التشغيل صار مستحيلاً بالتصميم. وقاعدة `NeverUseImplicitlyUnwrappedOptionals` في swift-format تمسك هذا النوع، وقد قالت عن الملف الخاطئ: `use 'Int' or 'Int?' instead of 'Int!'`.

والصورة التالية تجمع طرق التعامل مع الاختياري، ومتى تستخدم كل طريقة:

![مخطط قرار للتعامل مع قيمة اختيارية: إذا كان غيابها يعني فرعاً آخر في نفس المكان فاستخدم if let، وإذا كان يعني أن الدالة لا تستطيع أن تكمل فاستخدم guard let مع return أو throw، وإذا كانت لها قيمة افتراضية معقولة فاستخدم ?? أو dict[key, default:]، وإذا كنت تمر عبر سلسلة خصائص قد تغيب فاستخدم optional chaining مع ?، وإذا كانت القيمة شرطاً لوجود الكائن فضعها في init بدل Int!، وإذا كان غيابها خطأ مبرمج مستحيلاً ومشروحاً في تعليق فقط فاستخدم !، ومع كل طريقة مثال من المتجر](https://arabroot.io/content/images/2026/10/swift-idioms-05-optional-choices.webp)

ست طرق للتعامل مع الاختياري، والقوة بـ ! آخرها وأقلها استخداماً

## أخطاء الأرقام والنصوص Numbers and Strings

### 5\. خلط Int مع Double في عملية واحدة

```swift
let priceHalalas = 990
let withVat = priceHalalas * 1.15
print(withVat)
```

```text
02-wrong.swift:2:28: error: binary operator '*' cannot be applied to operands of type 'Int' and 'Double'
  |                            `- note: overloads for '*' exist with these partially matching parameter lists: (Double, Double), (Duration, Double), (Int, Int)
(did not compile)
```

في Java وPython وJavaScript يتحول العدد الصحيح إلى عشري تلقائياً، أما Swift فلا تحول أي نوع رقمي إلى آخر بدون طلب صريح، والرسالة تقول ذلك وتعرض الأشكال الموجودة للضرب. وأول ما يفعله المطور الجديد أن يكتب `Double(priceHalalas) * 1.15`، فيترجم الكود، ولكنه دخل في الخطأ التالي. والصحيح في المال أن يبقى الحساب كله بأعداد صحيحة:

```swift
let priceHalalas = 990
let vat = (priceHalalas * 15 + 50) / 100   // integer math, rounded half up
print(priceHalalas + vat, "halalas")
```

```text
1139 halalas
```

ولماذا لا تحول Swift الأرقام تلقائياً؟ لأن كل تحويل له ثمن مخفي: `Double` لا يمثل كل عدد صحيح بدقة بعد 2 أس 53، والتحويل من `Int64` إلى `Int32` قد يتجاوز الحد، والتحويل من عشري إلى صحيح يحذف الجزء العشري. واللغات التي تحول تلقائياً تخفي هذه الأثمان في سطور تبدو بريئة، أما Swift فتجعلك تكتب التحويل، فتراه في المراجعة وتسأل عنه.

### 6\. المال بـ Double

```swift
let unit = 9.90
let total = unit * 3
print(total, total == 29.70)
```

```text
29.700000000000003 false
exit=0
```

البرنامج عمل بدون أي تحذير، والنتيجة ليست 29.70، والمقارنة بها خاطئة. والسبب أن 9.90 لا يمكن تمثيلها بدقة في النظام الثنائي كما شرحنا في [Floating Point من الداخل](https://arabroot.io/articles/%D8%A7%D9%84%D8%A3%D8%B1%D9%82%D8%A7%D9%85-%D8%A7%D9%84%D8%B9%D8%B4%D8%B1%D9%8A%D8%A9-floating-point-%D9%85%D9%86-%D8%A7%D9%84%D8%AF%D8%A7%D8%AE%D9%84/)، وهذه ليست مشكلة Swift، وإنما مشكلة كل لغة، ولكن Swift تجعلها أوضح لأنها تطبع الرقم كاملاً. والإصلاح إما الهللات بأعداد صحيحة، أو النوع `Decimal` من Foundation الذي يحسب بالنظام العشري:

```swift
import Foundation
let unitHalalas = 990
print(unitHalalas * 3, unitHalalas * 3 == 2970)
let d = Decimal(string: "9.90")! * 3
print(d, d == Decimal(string: "29.70")!)
```

```text
2970 true
29.7 true
```

ولاحظ أننا أنشأنا `Decimal` من نص وليس من الرقم `9.90`، لأن `Decimal(9.90)` يبدأ من القيمة العشرية الثنائية غير الدقيقة نفسها. وفي هذا المسار كله نستخدم الهللات، لأن قاعدة البيانات تخزنها كأعداد صحيحة، والضريبة 15% تحسب بالتقريب للأعلى بدون أي كسور عشرية.

والاختيار بين الهللات و`Decimal` يعتمد على ما حولك: الهللات أسرع وأبسط وتطابق عموداً `int` في PostgreSQL بلا أي تحويل، ولكنها تفترض أن أصغر وحدة هي الهللة، فإذا احتجت أسعاراً بثلاث خانات عشرية مثل أسعار الوقود أو أسعار الصرف فـ `Decimal` مع عمود `numeric` أنسب. والممنوع في الحالتين هو `Double`.

### 7\. فهرسة النص برقم String Indexing

```swift
let name = "قهوة خولاني"
print(name[0])
```

```text
04-wrong.swift:2:7: error: 'subscript(_:)' is unavailable: cannot subscript String with an Int, use a String.Index instead.
Swift.String.subscript:3:10: note: 'subscript(_:)' has been explicitly marked unavailable here
(did not compile)
```

الرسالة لطيفة: الفهرسة برقم غير متاحة عمداً، واستخدم `String.Index`. والسبب ما رأيناه في المقال الأول: الحرف في Swift هو ما يراه الإنسان حرفاً، وقد يكون بايتين أو أربعة أو أكثر، فالوصول إلى الحرف رقم 1000 يحتاج المرور على كل ما قبله، واللغة ترفض أن تخفي هذه التكلفة خلف صيغة تبدو رخيصة. والصحيح أن تستخدم ما يعبر عن قصدك:

```swift
print(name.first!, name.prefix(4), name[name.index(name.startIndex, offsetBy: 5)])
```

```text
ق قهوة خ
```

و`first` و`prefix` و`dropFirst` و`split` تكفي في أغلب الحالات، فإذا وجدت نفسك تحسب الفهارس كثيراً فغالباً تحتاج أن تحول النص إلى مصفوفة `Array(name)` مرة واحدة، أو أن تعمل على `utf8` إذا كان النص بروتوكولاً وليس كلاماً.

وهذا مهم في العربية تحديداً، فالكلمة المكتوبة بالحركات حروفها بعين الإنسان قليلة، ولكن كل حركة فيها نقطة ترميز Code Point منفصلة، وSwift تجمع الحرف مع حركاته في حرف واحد Grapheme Cluster كما يشرح فصل [Strings and Characters](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/stringsandcharacters/?ref=arabroot.io)، بينما Java وJavaScript تعدان وحدات UTF-16، فتعطي نفس الكلمة طولاً مختلفاً في كل لغة. فإذا كانت لديك خدمة بلغتين تقارنان طول نص عربي فتأكد أنهما تعدان بنفس الوحدة.

### 8\. طول النص بالحروف Characters وحد الحقل بالبايتات Bytes

حقل اسم المرسل في مزود الرسائل محدود بعشرين بايتاً، والمطور فحص الطول بـ `count`:

```swift
let label = "قهوة عربية ☕️"
if label.count <= 20 { print("accepted, count =", label.count, "but bytes =", label.utf8.count) }
```

```text
accepted, count = 12 but bytes = 26
```

اثنا عشر حرفاً بعين الإنسان، وستة وعشرون بايتاً في الحقيقة: تسعة حروف عربية كل منها بايتان، ومسافتان، والفنجان مع محدد الشكل ستة بايتات. فالفحص نجح، والمزود قطع الرسالة أو رفضها. والإصلاح أن تقيس بنفس الوحدة التي يقيس بها الطرف الآخر:

```swift
let limit = 20
print(label.utf8.count <= limit ? "fits" : "too long: \(label.utf8.count) bytes > \(limit)")
```

```text
too long: 26 bytes > 20
```

والقاعدة: `count` للعرض على الشاشة وحدود الإدخال التي يراها المستخدم، و`utf8.count` لقواعد البيانات والبروتوكولات وحقول البايتات، و`utf16.count` عندما تتعامل مع واجهات JavaScript أو Java التي تعد بوحدات UTF-16.

![النص قهوة عربية مع فنجان قهوة مقسوماً إلى 12 حرفاً كما يراها الإنسان، وتحت كل حرف عدد بايتاته في UTF-8: الحروف العربية بايتان والمسافة بايت والفنجان مع محدد الشكل ستة بايتات والمجموع 26، ومع ثلاثة أسطر: count يساوي 12 للشاشة، وutf8.count يساوي 26 لقواعد البيانات والبروتوكولات، وname[0] لا يترجم واستخدم first وprefix وString.Index](https://arabroot.io/content/images/2026/10/swift-idioms-02-strings.webp)

الحرف ما يراه الإنسان، والبايتات ما يراه البروتوكول

## أخطاء القيم والمراجع Value Semantics

### 9\. الحلقة التي تعدل نسخة for var

المطور القادم من Java أراد أن ينقص قطعة من مخزون كل منتج:

```swift
struct Product { let name: String; var stock: Int }
var products = [Product(name: "تمر", stock: 10), Product(name: "هيل", stock: 4)]
for var p in products { p.stock -= 1 }        // compiles: p is a copy
print(products.map(\.stock))
```

```text
[10, 4]
exit=0
```

الكود ترجم وعمل، والمخزون لم يتغير. وفي البداية كتب `for p in products` فرفض المترجم لأن `p` ثابت، فأضاف `var` ليسكته، وهنا الفخ: `p` نسخة من المنتج لأن `Product` قيمة، والتعديل على النسخة يموت معها في نهاية الدورة. وفي Java كان سيعمل لأن `p` مرجع إلى الكائن نفسه. والإصلاح أن تعدل العنصر في مكانه بالفهرس، أو أن تبني مصفوفة جديدة:

```swift
for i in products.indices { products[i].stock -= 1 }
print(products.map(\.stock))
// or: products = products.map { var p = $0; p.stock -= 1; return p }
```

```text
[9, 3]
```

والأسلوب الأوضح في Swift لهذا النوع من التعديل أن تكتب دالة على النوع نفسه بـ `mutating`، مثل `mutating func sell(_ qty: Int)`، ثم تستدعيها على العنصر في مكانه `products[i].sell(1)`، أو أن تمرر المصفوفة إلى دالة بـ `inout` إذا كانت الدالة يجب أن تعدلها. وكلمتا `mutating` و`inout` تقولان للقارئ صراحة أين تتغير القيم، وهذا ما لا تراه في Java حيث كل دالة تستطيع أن تغير أي كائن يصلها.

### 10\. الكلاس المشترك Shared Class حيث تتوقع نسخة

والخطأ المعاكس: سطر سلة جاهز يستخدم كقالب، والمطور يظن أن وضعه في سلتين يعطي سطرين:

```swift
final class Line { var qty: Int; init(qty: Int) { self.qty = qty } }
let template = Line(qty: 1)
var cartA = [template]
var cartB = [template]          // "copy" of the array, same object inside
cartB[0].qty = 5
print("cartA:", cartA[0].qty, "cartB:", cartB[0].qty)
```

```text
cartA: 5 cartB: 5
```

المصفوفتان قيمتان منفصلتان فعلاً، ولكن العنصر داخل كل منهما مرجع إلى نفس الكائن، فعميل غير كميته في سلته فتغيرت سلة عميل آخر. وهذا الخطأ أخطر في Swift منه في Java، لأن المطور الذي تعلم أن «المصفوفة قيمة تنسخ» يظن أن كل ما فيها ينسخ أيضاً. والإصلاح أن يكون السطر قيمة كما يجب أن يكون:

```swift
struct Line { var qty: Int }
```

```text
cartA: 1 cartB: 5
```

![لوحتان: في اليمين for var p in products حيث p نسخة من كل منتج فيتغير المخزون في النسخة وتبقى المصفوفة 10 و4، والإصلاح for i in products.indices فتصبح 9 و3، وفي اليسار مصفوفتان cartA وcartB كل منهما قيمة ولكن العنصر فيهما مرجع إلى نفس كائن Line فتصبح الكمية 5 في السلتين، والإصلاح struct Line فتبقى 1 في الأولى و5 في الثانية](https://arabroot.io/content/images/2026/10/swift-idioms-03-value-traps.webp)

فخان متعاكسان: نسخة تظنها الأصل، ومرجع تظنه نسخة

### 11\. الإغلاق الذي يمسك self Retain Cycle

```swift
final class Checkout {
    var onDone: (() -> Void)?
    let id: Int
    init(id: Int) { self.id = id; onDone = { print("checkout \(self.id) done") } }   // self captured strongly
    deinit { print("Checkout \(id) freed") }
}
do { let c = Checkout(id: 1); c.onDone?() }
print("end of scope")
```

```text
checkout 1 done
end of scope
```

لا يوجد سطر «Checkout 1 freed»، أي أن الكائن لم يحرر بعد خروجه من النطاق، لأن الكائن يمسك الإغلاق في `onDone`، والإغلاق يمسك الكائن عبر `self`، وهذه الدورة المرجعية التي قسنا ثمنها في المقال الثاني. وفي Java وJavaScript لا مشكلة لأن جامع القمامة يرى الدورة، وفي Swift لا يوجد جامع. والإصلاح قائمة الالتقاط Capture List:

```swift
onDone = { [weak self] in print("checkout \(self?.id ?? -1) done") }
```

```text
checkout 1 done
Checkout 1 freed
end of scope
```

و`[weak self]` تجعل `self` داخل الإغلاق اختيارياً، والأسلوب المعتاد في الإغلاقات الطويلة أن تبدأ بـ `guard let self else { return }`. ولا تحتاجها في كل إغلاق، فالإغلاق الذي يعمل ويختفي، مثل `map` و`filter`، لا يخزن في أي مكان ولا يصنع دورة، وإنما تحتاجها عندما يخزن الإغلاق في خاصية أو في كائن يعيش طويلاً.

وأين يظهر هذا الخطأ في الواقع؟ في تطبيقات iOS يظهر في المؤقتات Timers ومراقبي الإشعارات والإغلاقات التي تمرر إلى الشاشات، فتبقى الشاشة في الذاكرة بعد إغلاقها، وتكشفه أداة Memory Graph في Xcode. وعلى السيرفر يظهر في الكائنات الطويلة العمر التي تخزن معالجات، مثل عميل يعيد الاتصال تلقائياً ويخزن إغلاقاً يستدعيه عند كل رسالة، فتبقى الاتصالات القديمة حية وتزيد الذاكرة ببطء، كما رأينا مع أداة المحاسبة في المقال الثاني.

## أخطاء البروتوكولات والتعداد Protocols and Enums

### 12\. الدالة في الامتداد التي لا تستدعى Static Dispatch

هذا الخطأ يفاجئ حتى من يكتب Swift منذ سنوات:

```swift
protocol Receipt { func title() -> String }
extension Receipt {
    func title() -> String { "فاتورة" }
    func footer() -> String { "شكراً لك" }          // not a protocol requirement
}
struct VatReceipt: Receipt {
    func title() -> String { "فاتورة ضريبية" }
    func footer() -> String { "الرقم الضريبي 300000000000003" }
}
let r: any Receipt = VatReceipt()
print(r.title(), "|", r.footer())
```

```text
فاتورة ضريبية | شكراً لك
```

العنوان جاء من `VatReceipt`، والتذييل جاء من الامتداد، رغم أن `VatReceipt` كتبت `footer` خاصة بها، والفاتورة الضريبية خرجت بدون الرقم الضريبي. والسبب أن `title` متطلب في البروتوكول، فاستدعاؤه عبر `any Receipt` يمر بجدول البروتوكول ويصل إلى تنفيذ النوع الحقيقي، أما `footer` فليست في البروتوكول، وإنما دالة في الامتداد فقط، فيختار المترجم التنفيذ بحسب النوع المعلن للمتغير، وهو `any Receipt`، أي تنفيذ الامتداد. وفي Java كل دالة في الواجهة تستدعى ديناميكياً، ولذلك لا يتوقع القادم منها هذا السلوك. والإصلاح أن تضيف الدالة إلى البروتوكول نفسه إذا كانت الأنواع ستغيرها:

```swift
protocol Receipt { func title() -> String; func footer() -> String }   // footer is now a requirement
```

```text
فاتورة ضريبية | الرقم الضريبي 300000000000003
```

وسبب هذا التصميم الأداء والوضوح معاً: الدالة الموجودة في الامتداد فقط تستدعى استدعاء مباشراً Static Dispatch يعرفه المترجم وقت الترجمة، فيستطيع أن يدمجها في مكان الاستدعاء، بينما متطلبات البروتوكول تمر عبر جدول وقت التشغيل. والقاعدة التي تريحك: إذا كانت الدالة سلوكاً افتراضياً قد تغيره الأنواع فضعها في البروتوكول ثم أعط تنفيذها الافتراضي في الامتداد، وإذا كانت دالة مساعدة مبنية على المتطلبات ولا يجب أن تغيرها الأنواع فاتركها في الامتداد فقط.

### 13\. default التي تبتلع الحالة الجديدة

أضاف الفريق طريقة دفع جديدة هي تابي، وفي دالة حساب الرسوم `default` قديمة:

```swift
enum Payment { case mada, applePay, cash, tabby }    // tabby added last week
func fee(_ p: Payment) -> Int {
    switch p {
    case .mada: return 0
    case .applePay: return 0
    default: return 0                                 // tabby silently lands here
    }
}
print("tabby fee:", fee(.tabby))
```

```text
tabby fee: 0
```

الحالة الجديدة دخلت في `default` بصمت، ورسوم تابي صارت صفراً دون أن يقرر أحد ذلك. وأقوى ما في `enum` في Swift أن المترجم يفحص شمول `switch`، و`default` تعطل هذا الفحص. فإذا حذفتها وعالجت الحالات صراحة:

```text
13-right.swift:3:5: error: switch must be exhaustive
  |     |- error: switch must be exhaustive
  |     `- note: add missing case: '.tabby'
(did not compile)
```

وهذا خطأ ترجمة تريده، لأنه يشير إلى المكان بالضبط ويقول ما الحالة الناقصة، فتكتب القرار صراحة:

```swift
case .tabby: return 350          // the compiler pointed here; now the decision is explicit
```

```text
tabby fee: 350
```

والقاعدة: لا تكتب `default` على `enum` تملكه أنت، واكتبها فقط على تعداد من مكتبة خارجية قد تضيف حالات لا تعرفها، وهناك تستخدم `@unknown default` التي تبقي التحذير عند ظهور حالة جديدة.

والفرق بين التعدادين مقصود في اللغة، فالمكتبة التي تبنى مع استقرار الواجهة الثنائية مثل مكتبات Apple يمكن أن تضيف حالات في إصدار جديد، ولذلك يجبرك المترجم على حالة احتياطية، بينما تعداد مشروعك أنت مغلق، ويعرف المترجم كل حالاته، فيستطيع أن يحرسها لك ما دمت لم تكتب `default`.

## أخطاء التزامن Concurrency

### 14\. الانتظار الحاجب داخل async

القادم من Java اعتاد `Thread.sleep`، فكتبها داخل مهام متزامنة:

```swift
await withTaskGroup(of: Void.self) { g in
    for _ in 0..<16 { g.addTask { Thread.sleep(forTimeInterval: 0.5) } }   // blocks a pool thread
}
```

```text
11-wrong.swift:5:42: error: class method 'sleep' is unavailable from asynchronous contexts
(did not compile)
```

ووضع Swift 6 رفضها، وهذا جيد. ولكن نفس الفكرة بدالة من C لا يعرف المترجم أنها تحجب تمر:

```swift
for _ in 0..<16 { g.addTask { usleep(500_000) } }   // C call: the compiler cannot see that it blocks
```

```text
16 x usleep(0.5 s) on 2 cores: 1.02312618 seconds
```

والطريقة الصحيحة التي تعلق المهمة ولا تحجب الخيط:

```swift
for _ in 0..<16 { g.addTask { try? await Task.sleep(for: .milliseconds(500)) } }   // suspends, frees the thread
```

```text
16 x Task.sleep(0.5) on 2 cores: 0.520727282 seconds
```

ست عشرة مهمة كل منها تنتظر نصف ثانية: مع `Task.sleep` انتهت كلها في نصف ثانية، لأن الانتظار لا يأخذ خيطاً، ومع `usleep` أخذت ثانية كاملة، أي دفعتين من ثماني مهام، لأن كل مهمة حجبت خيطاً من المجمع وعدد خيوطه ثمانية هنا. وفي [مقال التزامن](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B2%D8%A7%D9%85%D9%86-%D9%81%D9%8A-swift-async-await-%D9%88%D8%A7%D9%84%D9%85%D9%85%D8%AB%D9%84%D9%88%D9%86-actors/) رأينا أن ثماني مهام حاجبة تكفي لتجميد العملية كلها إذا كانت تنتظر بعضها. والانتظار الحقيقي في الخدمات ليس `sleep` وإنما مكتبة قديمة تقرأ من الشبكة أو الملفات بطريقة حاجبة، فابحث عن نسخة `async` منها.

وكيف تجدها في مشروع كبير؟ ابحث في الكود عن `wait()` و`sleep` و`Data(contentsOf:)` مع الروابط، و`FileHandle.readDataToEndOfFile()`، وأي دالة من مكتبة C داخل دوال `async`. وعلى السيرفر بالذات راقب زمن الطلبات البسيطة مثل `/health` تحت الضغط، فإذا صعد زمنها مع أن عملها لا شيء تقريباً، فهناك من يحجب خيوط المجمع أو حلقات الأحداث.

### 15\. الكلاس المشترك بين المهام Sendable

```swift
final class Cart { var total = 0 }
let cart = Cart()
await withTaskGroup(of: Void.self) { g in
    for _ in 0..<4 { g.addTask { cart.total += 1 } }
}
print(cart.total)
```

```text
14-wrong.swift:4:34: error: non-Sendable type 'Cart' of let 'cart' cannot exit main actor-isolated context [#NonSendableExitingActor]
  |             `- note: class 'Cart' does not conform to the 'Sendable' protocol
14-wrong.swift:4:34: error: main actor-isolated let 'cart' cannot be accessed from outside of the actor
(did not compile)
```

الكود في أعلى الملف معزول على الممثل الرئيسي، والمتغير `cart` كلاس غير `Sendable`، فلا يسمح المترجم أن يخرج إلى مهام تعمل بالتوازي. وهذا ما كان سيصبح سباق بيانات في Java بدون أي تحذير. وأول ما يفعله المطور المستعجل أن يكتب `@unchecked Sendable`، وقد رأينا في مقال التزامن أنها أضاعت أكثر من نصف التحديثات. والإصلاح الصحيح ممثل:

```swift
actor Cart { var total = 0; func add(_ n: Int) { total += n } }
let cart = Cart()
await withTaskGroup(of: Void.self) { g in
    for _ in 0..<4 { g.addTask { await cart.add(1) } }
}
print(await cart.total)
```

```text
4
```

ولاحظ أن المترجم في Swift 6 لا يرفض كل كلاس غير `Sendable` يعبر إلى مهمة، فإذا أنشأت الكائن ثم أرسلته إلى مهمة واحدة ولم تستخدمه بعدها، فالتحليل بالمناطق Regions يقبل ذلك لأنه لا يوجد طرفان. والرفض هنا لأن الكائن نفسه يصل إليه أربع مهام والكود الرئيسي معاً، وهذا بالضبط تعريف الحالة المشتركة التي تحتاج ممثلاً أو قفلاً.

## مراجعة طلب دمج كامل Code Review: قبل وبعد

الأخطاء نادراً ما تأتي وحدها، فهذا طلب دمج حقيقي الشكل من المطور القادم من Java، يحسب إجمالي السلة مع الضريبة، وفيه خمس عادات من لغات أخرى في عشرة أسطر:

```swift
class CartService {
    var prices: [Int: Double] = [1: 9.90, 2: 45.00]
    func total(items: [(Int, Int)]) -> Double {
        var t = 0.0
        for item in items { t += prices[item.0]! * Double(item.1) }
        return t * 1.15
    }
}
let svc = CartService()
if CommandLine.arguments.count == 1 { print("cart 1:", svc.total(items: [(1, 3)])) }
else { print("cart 2:", svc.total(items: [(1, 3), (9, 1)])) }
```

```text
cart 1: 34.155
exit=0
p/16-review-before.swift:6: Fatal error: Unexpectedly found nil while unwrapping an Optional value
exit=132
```

السلة الأولى أعطت 34.155، أي ثلاث خانات عشرية لمبلغ بالريال، وأي تقريب بعدها يقرره من يعرض الرقم، فالشاشة قد تعرض 34.16 والفاتورة 34.15\. والسلة الثانية فيها منتج غير موجود فتوقف البرنامج كله، وفي خدمة حقيقية هذا يعني أن طلباً واحداً بمنتج محذوف أسقط العملية التي تخدم كل العملاء. وفي المراجعة نقرأ الكود سطراً بسطر: `class` بلا هوية ولا حالة تتغير، و`var` لأسعار لا تتغير بعد الإنشاء، و`Double` للمال، وTuple بلا أسماء فلا يعرف القارئ أي الرقمين الكمية، و`!` على قاموس، والضريبة تحسب على المجموع العشري ولا تقرب إلى هللة. وبعد المراجعة صار الكود كما يلي:

```swift
struct CartService {
    enum Problem: Error { case unknownProduct(Int) }
    struct Totals { let subtotal: Int; let vat: Int; var total: Int { subtotal + vat } }

    let pricesHalalas: [Int: Int]

    func totals(of lines: [(productId: Int, qty: Int)]) throws(Problem) -> Totals {
        var subtotal = 0
        for line in lines {
            guard let price = pricesHalalas[line.productId] else { throw .unknownProduct(line.productId) }
            subtotal += price * line.qty
        }
        return Totals(subtotal: subtotal, vat: (subtotal * 15 + 50) / 100)   // VAT once, on the subtotal
    }
}
```

```text
subtotal 2970 vat 446 total 3416 halalas
rejected: unknownProduct(9)
exit=0
```

في الكود الجديد لاحظ التالي:

- الخدمة `struct` فيها خاصية `let` واحدة، فلا يمكن أن يغير أحد الأسعار في منتصف الحساب، وهي `Sendable` تلقائياً، فتستطيع أن تمررها إلى أي مهمة متزامنة بدون أي كلمة إضافية.
- المال أعداد صحيحة من أول سطر إلى آخره، والضريبة محسوبة مرة واحدة على المجموع ومقربة للأعلى إلى هللة، فالرقم 3416 هو نفسه في الشاشة والفاتورة والتقرير.
- المنتج غير الموجود صار خطأ من نوع معروف `Problem.unknownProduct(9)`، يحوله معالج الطلب إلى رد 400 أو 409، ولا يسقط العملية.
- أسماء المعاملات `totals(of:)` و`productId` و`qty` تجعل الاستدعاء يقرأ كجملة، ولا يحتاج القارئ أن يتذكر أي عنصر في الـ Tuple هو الكمية.
- عدد الأسطر تقريباً نفسه، فالأسلوب الصحيح في Swift ليس أطول، وإنما يضع المعلومات في الأنواع بدل التعليقات والذاكرة.

وطريقة المراجعة نفسها مهمة بقدر الكود، فالمطور الجديد لا يتعلم من قائمة ملاحظات طويلة، وإنما من سؤال واحد في كل ملاحظة: ماذا يقول النوع؟ فبدل «لا تستخدم Double» تكتب «هل يقبل هذا النوع 34.155 ريالاً؟»، وبدل «احذف !» تكتب «ماذا يحدث إذا حذف المنتج من الكتالوج وما زال في سلة عميل؟». وهذه الأسئلة تبني عند المطور العادة الصحيحة، فبعد أسابيع قليلة سوف يسألها لنفسه قبل أن يرسل الطلب، وهذا هو الهدف الحقيقي من المراجعة، وليس إصلاح هذا الطلب وحده.

## اجعل الأداة تمسكها: قواعد swift-format Lint Rules

ثمانية من الأخطاء الخمسة عشر لا يمسكها المترجم، والمراجعة البشرية تتعب، ولذلك فإن الخطوة التالية أن تجعل أداة الفحص تمسك ما تستطيع. والمنسق `swift-format` الذي رأيناه في المقال الأول فيه قواعد معطلة افتراضياً لهذا الغرض، كما في [توثيق القواعد](https://github.com/swiftlang/swift-format/blob/main/Documentation/RuleDocumentation.md?ref=arabroot.io)، فنفعلها في ملف الإعداد:

```json
{
  "version": 1,
  "indentation": { "spaces": 4 },
  "lineLength": 160,
  "rules": {
    "NeverForceUnwrap": true,
    "NeverUseForceTry": true,
    "NeverUseImplicitlyUnwrappedOptionals": true
  }
}
```

ثم نفحص الملف الخاطئ الأول ومعه ثلاثة ملفات من الملفات «الصحيحة»:

```bash
swift format lint --configuration .swift-format m/01-wrong.swift m/03-right.swift m/10-right.swift m/04-right.swift
swift format lint --strict --configuration .swift-format m/01-wrong.swift
```

```text
m/01-wrong.swift:3:12: warning: [NeverForceUnwrap] do not force unwrap 'stock[wanted]'
m/01-wrong.swift:3:26: warning: [Spacing] remove 1 space
m/03-right.swift:4:9: warning: [NeverForceUnwrap] do not force unwrap 'Decimal(string: "9.90")'
m/03-right.swift:5:15: warning: [NeverForceUnwrap] do not force unwrap 'Decimal(string: "29.70")'
m/10-right.swift:10:41: warning: [NeverUseForceTry] do not use force try
m/04-right.swift:2:7: warning: [NeverForceUnwrap] do not force unwrap 'name.first'
exit=0
strict exit=1
```

في المخرج أعلاه لاحظ التالي:

- الأداة أمسكت الخطأ الأول في مكانه بالضبط، السطر 3 العمود 12، وهو الذي أوقف البرنامج.
- وأمسكت أيضاً علامات `!` و`try!` في ملفاتنا «الصحيحة»، وهي علامات وضعناها عمداً لأن الملفات تجارب قصيرة، وهذا بالضبط ما تريده من القاعدة: أن تسألك عن كل واحدة، فإما أن تزيلها أو تعطل القاعدة لذلك السطر وحده بتعليق يشرح السبب.
- رمز الخروج صفر رغم التحذيرات، ومع `--strict` صار 1، وهذا ما تضعه في CI حتى يفشل البناء.

وبقية الأخطاء الصامتة، مثل `for var` والمال بـ `Double` و`default` على التعداد، لا توجد لها قواعد في swift-format، وبعضها يمكن أن تكتب له قاعدة مخصصة بتعبير نمطي في [SwiftLint](https://github.com/realm/SwiftLint?ref=arabroot.io)، وما بقي تمسكه الاختبارات والمراجعة. ولذلك قائمة التحقق في آخر المقال موجهة للمراجع البشري.

## الأسلوب الحديث في Swift Idiomatic Swift: ما الذي يجمع هذه الإصلاحات؟

إذا نظرت إلى الإصلاحات الخمسة عشر معاً فستجد أنها كلها تطبيق لبضع أفكار، وهذه الأفكار هي ما يقصده الناس بـ «Swift الحديثة»:

- **اجعل النوع يقول الحقيقة:** القيمة التي قد تغيب اختيارية، والحالات المحدودة `enum`، والمال عدد صحيح بالهللات، والخطأ المتوقع `throws` بنوع إذا أمكن. وكلما قال النوع أكثر قل ما تفحصه بنفسك.
- **القيم أولاً:** `struct` و`let` افتراضياً، و`class` و`var` عندما تحتاجهما فعلاً، والممثل للحالة المشتركة بين المهام.
- **دع المترجم يعمل:** لا `default` على تعدادك، ولا `!` بلا سبب، ولا `@unchecked` بلا تعليق، ولا تحذيرات متراكمة. فكل واحدة منها مكان أطفأت فيه فحصاً كان سيمسك خطأ.
- **الوضوح عند الاستدعاء:** أسماء المعاملات تجعل الاستدعاء جملة، مثل `reserve(productId, qty: 3)`، وهذا ما تشرحه [إرشادات تصميم الواجهات API Design Guidelines](https://www.swift.org/documentation/api-design-guidelines/?ref=arabroot.io)، وأهم قاعدة فيها أن الوضوح عند نقطة الاستخدام أهم من الاختصار.
- **البروتوكولات والامتدادات قبل الوراثة:** بروتوكول صغير وامتداد يعطي سلوكاً افتراضياً، مع الانتباه إلى أن ما لا يكون متطلباً في البروتوكول لا يستدعى ديناميكياً.
- **التزامن المنظم:** `async let` و`TaskGroup`، ولا انتظار حاجب داخل `async`، والإلغاء يفحص في العمل الطويل.

والجدول التالي يجمع الأخطاء وإصلاحاتها في مكان واحد لتستخدمه في مراجعة الكود:

| الخطأ                         | من يمسكه           | الأسلوب الصحيح                              |
| ----------------------------- | ------------------ | ------------------------------------------- |
| dict\[key\]!                  | توقف وقت التشغيل   | if let وguard let وdict\[key, default:\]    |
| var x: Int!                   | توقف وقت التشغيل   | القيمة في init كخاصية let                   |
| اختياري داخل نص               | تحذير              | افتح الاختياري أولاً أو ?? "..."            |
| try? على عملية مهمة           | لا أحد             | do/catch وطباعة الخطأ                       |
| Int \* Double                 | المترجم            | أعداد صحيحة بالهللات                        |
| المال بـ Double               | لا أحد             | هللات أو Decimal(string:)                   |
| text\[0\]                     | المترجم            | first وprefix وString.Index                 |
| count لحد بالبايتات           | لا أحد             | utf8.count                                  |
| for var p in array            | لا أحد             | for i in array.indices أو map               |
| كلاس حيث تكفي قيمة            | لا أحد             | struct                                      |
| إغلاق مخزن يمسك self          | لا أحد             | \[weak self\]                               |
| دالة في امتداد فقط            | لا أحد             | أضفها إلى البروتوكول                        |
| default على تعدادك            | لا أحد             | كل الحالات صراحة، و@unknown default للخارجي |
| Thread.sleep وusleep في async | المترجم للأولى فقط | Task.sleep ودوال async                      |
| كلاس مشترك بين المهام         | المترجم في Swift 6 | actor أو Mutex                              |

## ما الذي لم تغطه هذه المرحلة؟ Beyond the Basics

خمسة مقالات لا تغطي لغة كاملة، وقد اخترنا ما يحتاجه من يبدأ العمل فعلاً، وتركنا أشياء سوف تقابلها قريباً. أولها الماكرو Macros، وقد رأيت منها `#expect` و`@Test`، وهي كود يعمل وقت الترجمة ويولد كوداً، وتستطيع أن تكتب ماكرو خاصاً بك. وثانيها بناة النتائج Result Builders، وهي التي تجعل كود SwiftUI يبدو كوصف للواجهة وليس كاستدعاءات دوال، وتستخدم أيضاً في مكتبات توليد HTML وSQL. وثالثها أغلفة الخصائص Property Wrappers التي رأيتها في `@Argument` و`@Option`. ورابعها الأنواع العامة المتقدمة، مثل الأنواع المرتبطة Associated Types في البروتوكولات والقيود بـ `where`. وخامسها التشغيل البيني مع C وC++ وJava، وهو من أكثر ما تطور في الإصدارات الأخيرة. وكلها في الكتاب الرسمي وفي مقترحات Swift Evolution، وخطة القراءة التالية تمر عليها بالترتيب. وإذا كنت تقارن Swift بغيرها من اللغات لمشروع جديد فارجع إلى مقال [كيف تختار لغة البرمجة لمشروعك](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D8%AA%D8%AE%D8%AA%D8%A7%D8%B1-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%84%D9%85%D8%B4%D8%B1%D9%88%D8%B9%D9%83/)، ففيه نفس خدمة الطلبات كمعيار للمقارنة.

## خطة قراءة Reading Plan من المصادر الرسمية

بعد هذه المرحلة أنت تعرف ما يكفي لتعمل، والخطوة التالية أن تقرأ المصادر الرسمية بترتيب يبني كل خطوة على ما قبلها، بدل أن تقفز بين المقالات والفيديوهات. وكلها مجانية ومفتوحة، والترتيب التالي هو ما نقترحه لمن يعرف لغة أخرى:

1. [**A Swift Tour**](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/guidedtour/?ref=arabroot.io) في كتاب اللغة، وهي جولة في صفحة واحدة تغطي ما رأيته في المقال الأول وأكثر، اقرأها في ساعة وجرب كل مثال بـ `swift` في سطر الأوامر.
2. [**The Swift Programming Language**](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/?ref=arabroot.io)، الكتاب الرسمي نفسه، ولا تقرأه من أوله إلى آخره، وإنما بهذا الترتيب: The Basics، ثم Strings and Characters، ثم Structures and Classes، ثم Enumerations، ثم Error Handling، ثم Protocols وGenerics، ثم Automatic Reference Counting، ثم Concurrency. وهذه الفصول هي التي تغطي الأخطاء الخمسة عشر في هذا المقال.
3. [**API Design Guidelines**](https://www.swift.org/documentation/api-design-guidelines/?ref=arabroot.io)، صفحة واحدة تشرح كيف تسمي الدوال والمعاملات، وهي ما يجعل كودك يشبه المكتبة القياسية وليس Java مكتوبة بصياغة Swift.
4. [**توثيق المكتبة القياسية**](https://www.swift.org/documentation/standard-library/?ref=arabroot.io)، وهو موقع التوثيق الجديد الذي أعلن مع Swift 6.4، وارجع إليه عند كل نوع تستخدمه، مثل `String` و`Array` و`Dictionary` و`Result`.
5. [**دليل الترحيل إلى Swift 6**](https://www.swift.org/migration/documentation/migrationguide/?ref=arabroot.io)، حتى لو لم يكن لديك كود قديم، لأنه أوضح شرح لقواعد `Sendable` والعزل ولماذا يرفض المترجم ما يرفضه.
6. [**Swift on Server**](https://www.swift.org/documentation/server/?ref=arabroot.io) وأدلته عن البناء والتغليف والنشر والأداء وتسرب الذاكرة على Linux، إذا كان عملك على السيرفر، ومعه [توثيق Vapor](https://docs.vapor.codes/?ref=arabroot.io).
7. [**Swift Testing**](https://developer.apple.com/documentation/testing?ref=arabroot.io) لكتابة الاختبارات بالأسلوب الحديث، و[دليل بناء مكتبة بـ SwiftPM](https://www.swift.org/getting-started/library-swiftpm/?ref=arabroot.io) لتنظيم الحزم.
8. [**Swift Evolution**](https://www.swift.org/swift-evolution/?ref=arabroot.io) عندما تريد أن تعرف لماذا صممت ميزة بطريقة معينة، فكل ميزة لها مقترح مكتوب فيه المشكلة والبدائل التي رفضت، وقد رأيت روابط بعضها في هذه المرحلة. والنقاشات نفسها في [منتديات Swift](https://forums.swift.org/?ref=arabroot.io).
9. **إذا كان هدفك تطبيقات Apple:** [دروس SwiftUI](https://developer.apple.com/tutorials/swiftui?ref=arabroot.io) الرسمية من Apple، وهي تحتاج Xcode على Mac كما ذكرنا في المقال الأول، وكل ما تعلمته عن اللغة في هذه المرحلة ينطبق هناك كما هو.

وإذا أردت خطة زمنية فهذا تقسيم واقعي لمن يعمل بدوام كامل ويقرأ ساعة يومياً: الأسبوع الأول للجولة وفصول الأساسيات والنصوص والهياكل والتعداد، مع إعادة كتابة أداة صغيرة تعرفها من لغتك الحالية بـ Swift. والأسبوع الثاني للأخطاء والبروتوكولات والأنواع العامة وARC، مع اختبارات بـ Swift Testing لكل ما كتبته. والأسبوع الثالث للتزامن ودليل الترحيل، مع تحويل جزء من الأداة إلى مهام متوازية وتشغيلها مع Thread Sanitizer. والأسبوع الرابع للسيرفر أو للتطبيقات بحسب عملك. وأهم من الترتيب أن تكتب كوداً كل يوم وتقرأ رسائل المترجم كاملة، فرسائل Swift الحديثة، كما رأيت في هذا المقال، تشرح المشكلة وتقترح الإصلاح وتعطيك رابطاً إلى صفحة توثيق لكل نوع من الأخطاء.

![مسار قراءة بتسع خطوات من المصادر الرسمية: A Swift Tour في ساعة، ثم فصول كتاب The Swift Programming Language بترتيب The Basics وStrings وStructures and Classes وEnumerations وError Handling وProtocols وGenerics وARC وConcurrency، ثم API Design Guidelines، ثم توثيق المكتبة القياسية، ثم دليل الترحيل إلى Swift 6، ثم يتفرع المسار إلى Swift on Server وVapor للسيرفر وإلى دروس SwiftUI على Mac للتطبيقات، مع Swift Testing وSwift Evolution والمنتديات كمراجع دائمة](https://arabroot.io/content/images/2026/10/swift-idioms-04-reading-plan.webp)

من الجولة إلى الكتاب إلى الإرشادات، ثم السيرفر أو التطبيقات

## مشاكل واجهناها أثناء التجربة Troubleshooting

- **رمز الخروج 141 بدل 132:** في التشغيل الأول مررنا مخرج البرنامج إلى `head`، فأغلق `head` الأنبوب، وأخذ البرنامج `SIGPIPE` قبل أن نقرأ رمز التوقف الحقيقي. والحل حفظ المخرج في ملف ثم قراءته.
- **الرسائل غائبة مع `-O`:** نفس الخطأ الأول بوضع الإنتاج توقف بدون «Unexpectedly found nil»، فشغلنا كل الأمثلة بوضع التصحيح لتظهر الرسائل كما يراها المطور.
- **الخطأ الحاجب الذي لا يترجم:** أردنا أن نقيس `Thread.sleep` داخل المهام، فرفضها وضع Swift 6، وهذا بحد ذاته نتيجة، ثم قسنا الفكرة نفسها بـ `usleep` التي لا يعرف المترجم أنها تحجب.
- **الخطأ 13 يحتاج ملفين صحيحين:** الملف الذي يحذف `default` لا يترجم، وهذا المقصود، فأضفنا ملفاً ثالثاً فيه القرار الصريح.

## قائمة تحقق Checklist: مراجعة طلب دمج في Swift

- هل يوجد `!` أو `try!` أو `as!` بدون تعليق يشرح لماذا هي آمنة؟
- هل يوجد `try?` على عملية يحتاج من يقرأ السجلات أن يعرف سبب فشلها؟
- هل يوجد `Double` أو `Float` في أي حساب مالي؟
- هل يوجد `class` لنوع بيانات بلا هوية، أو `for var` يعدل عناصر مصفوفة؟
- هل توجد خاصية بنوع `T!` خارج واجهات iOS، بدل أن تأخذ قيمتها في `init`؟
- هل يوجد إغلاق مخزن في خاصية يستخدم `self` بدون `[weak self]`؟
- هل يوجد `default` في `switch` على تعداد من نفس المشروع؟
- هل توجد دالة في امتداد بروتوكول تعيد الأنواع تنفيذها دون أن تكون متطلباً؟
- هل توجد دالة حاجبة أو `@unchecked Sendable` داخل كود متزامن؟
- هل يبنى المشروع بدون تحذيرات، وهل يعمل `swift format lint` و`swift test` في CI؟

## الخلاصة

وصلنا لنهاية الموضوع ونهاية هذه المرحلة، وأهم ما فيه:

- من الأخطاء الخمسة عشر رفض المترجم أربعة، وحذر من واحد، وأوقف البرنامج في اثنين، وثمانية عملت بهدوء وأعطت نتيجة خاطئة، ومعها `usleep` التي مرت من المترجم حيث رفض `Thread.sleep`، وهذه هي التي تحتاج أن تعرفها.
- أخطاء الاختياري تصلح بقول ما تريد عند غياب القيمة: `if let` أو `guard let` أو قيمة افتراضية، و`try?` لا تستخدم حين يهمك السبب.
- المال أعداد صحيحة، وSwift لا تحول بين الأنواع الرقمية تلقائياً، والنص حروف بعين الإنسان، وحدود البايتات تقاس بـ `utf8.count`.
- القيم تنسخ والمراجع تشترك، و`for var` تعدل نسخة، والكلاس داخل مصفوفة يبقى مشتركاً، والإغلاق المخزن يحتاج `[weak self]`.
- ما ليس متطلباً في البروتوكول لا يستدعى ديناميكياً، و`default` على تعدادك تطفئ أقوى فحص في اللغة.
- في التزامن لا انتظار حاجب داخل `async`، والحالة المشتركة في ممثل، والمترجم في وضع Swift 6 يمسك أغلب هذه الأخطاء إذا لم تسكته.
- وفي مراجعة طلب الدمج الكامل اجتمعت خمس عادات في عشرة أسطر، فأعطت السلة 34.155 ريالاً وأسقط منتج محذوف العملية، وبعد المراجعة صار الناتج 3416 هللة وصار المنتج المحذوف خطأ معروف النوع يرد عليه السيرفر بدل أن يتوقف.
- وخطة القراءة تبدأ بـ A Swift Tour ثم فصول الكتاب الرسمي بترتيب الأخطاء، ثم إرشادات تصميم الواجهات، ثم السيرفر أو التطبيقات.

وهذه بقية مقالات المرحلة، ومقالات تكمل ما رأيناه هنا:

[![](https://arabroot.io/content/images/2026/10/swift-for-devs-cover-cover.webp)دليلSwift للمطورين: الأدوات والصياغة Syntax على Linuxثبتنا Swift 6.4.2 على Linux وبنينا مكتبة لحساب السعر والضريبة مع أداة سطر أوامر واختبارات، لترى اللغة مقارنة بما تعرفه وتعرف ما يحتاج Mac وما لا يحتاجه.](https://arabroot.io/articles/swift-%D9%84%D9%84%D9%85%D8%B7%D9%88%D8%B1%D9%8A%D9%86-%D8%A7%D9%84%D8%A7%D8%AF%D9%88%D8%A7%D8%AA-%D9%88%D8%A7%D9%84%D8%B5%D9%8A%D8%A7%D8%BA%D8%A9-%D8%B9%D9%84%D9%89-linux/)[![](https://arabroot.io/content/images/2026/10/swift-types-memory-cover-cover.webp)دليلالأنواع والذاكرة ARC والأخطاء Errors في Swiftقسنا النسخ عند الكتابة وتسرب الذاكرة بالدورة المرجعية داخل Container محدود، ورأينا كيف تفشل Swift بـ throws والاختياري والتوقف، لتعرف ما يختلف فيها عن Java وPython وGo.](https://arabroot.io/articles/%D8%A7%D9%84%D8%A7%D9%86%D9%88%D8%A7%D8%B9-%D9%88%D8%A7%D9%84%D8%B0%D8%A7%D9%83%D8%B1%D8%A9-arc-%D9%88%D8%A7%D9%84%D8%A7%D8%AE%D8%B7%D8%A7%D8%A1-%D9%81%D9%8A-swift/)[![](https://arabroot.io/content/images/2026/10/swift-concurrency-cover-cover.webp)دليلالتزامن في Swift: async/await والممثلون Actorsقسنا مئة طلب HTTP وأربعة ملايين عملية SHA-256 بالتتابع وبالتوازي في Swift، وصنعنا سباق بيانات وجموداً حقيقيين وأصلحناهما، لترى ما يرفضه المترجم وما يبقى عليك.](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B2%D8%A7%D9%85%D9%86-%D9%81%D9%8A-swift-async-await-%D9%88%D8%A7%D9%84%D9%85%D9%85%D8%AB%D9%84%D9%88%D9%86-actors/)[![](https://arabroot.io/content/images/2026/10/floating-point-cover-cover.webp)دليلFloating Point من الداخل: كيف يخزن الحاسب الكسوركيف يحفظ معيار IEEE 754 الكسور في البتات، ولماذا تكبر الفجوة بين الأرقام وتضيع خانات المعرفات، ومتى تحتاج النوع العشري.](https://arabroot.io/articles/%D8%A7%D9%84%D8%A3%D8%B1%D9%82%D8%A7%D9%85-%D8%A7%D9%84%D8%B9%D8%B4%D8%B1%D9%8A%D8%A9-floating-point-%D9%85%D9%86-%D8%A7%D9%84%D8%AF%D8%A7%D8%AE%D9%84/)[![](https://arabroot.io/content/images/2026/10/error-handling-cover-cover.webp)دليلكيف تتعامل لغات البرمجة مع الأخطاء؟لماذا ترمي Java وPython الاستثناءات بينما ترجع Go وRust الخطأ كقيمة، وكيف تمنع الخطأ من أن يختفي بصمت في كودك.](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D8%AA%D8%AA%D8%B9%D8%A7%D9%85%D9%84-%D9%84%D8%BA%D8%A7%D8%AA-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%85%D8%B9-%D8%A7%D9%84%D8%A7%D8%AE%D8%B7%D8%A7%D8%A1/)[![](https://arabroot.io/content/images/2026/10/debugging-basics-cover-cover.webp)دليلقراءة ال Stack Trace وتصحيح الأخطاء Debugging للمبتدئينكيف تقرأ رسالة الخطأ في Python وJavaScript سطراً بسطر، ومتى تستخدم الطباعة أو نقاط التوقف في VS Code، وكيف يجد git bisect التعديل الذي أدخل الخطأ.](https://arabroot.io/articles/%D9%82%D8%B1%D8%A7%D8%A1%D8%A9-%D8%B1%D8%B3%D8%A7%D9%84%D8%A9-%D8%A7%D9%84%D8%AE%D8%B7%D8%A7-stack-trace-%D9%88%D8%A7%D9%84%D8%AA%D8%B5%D8%AD%D9%8A%D8%AD-debugging/)[![](https://arabroot.io/content/images/2026/10/choose-language-cover-cover.webp)دليلكيف تختار لغة البرمجة Programming Language لمشروعك؟نختار لغة خدمة حقيقية بأسئلة الفريق والتوظيف والمكتبات والرخصة، ثم نكتبها بأربع لغات ونقيس البناء والحجم والذاكرة والطلبات في الثانية بأنفسنا.](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D8%AA%D8%AE%D8%AA%D8%A7%D8%B1-%D9%84%D8%BA%D8%A9-%D8%A7%D9%84%D8%A8%D8%B1%D9%85%D8%AC%D8%A9-%D9%84%D9%85%D8%B4%D8%B1%D9%88%D8%B9%D9%83/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال، وتشغيل الأخطاء الخمسة عشر وإصلاحاتها بـ Swift 6.4.2 في `swift:6.4.2-noble` بوضع Swift 6 داخل Container محدود بمعالجين و512 MB.