EduFlow
أساسيات Swift لتطبيقات iOS
⁦0%⁩

الوحدة 1 · لغة Swift

الـ optionals بلا أعطال

16 دقراءة 4 داكتشف الخطأ

الدرس في لمحة

اقرأ أنواع الـ optional، وفكّها بأمان باستخدام if let وguard let و??، واشرح لماذا يُعدّ الفكّ القسري عطلًا ينتظر لحظته.

  1. 1ما هو الـ optional
  2. 2if let: استخدم القيمة في فرع واحد
  3. 3guard let: اخرج مبكرًا وأبقِ المسار السعيد مستويًا
  4. 4?? والـ optional chaining
  5. 5الفكّ القسري

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

ينقر المستخدم على Add Expense، ويكتب “12.50” في حقل المبلغ، ثم ينقر Save. يستقبل كودك String. وتحويلها إلى رقم قد يفشل: ربما الحقل فارغ، أو ربما كتب المستخدم “twelve”. كثير من اللغات تعطيك في هذه الحالة NaN أو صفرًا أو استثناءً. أما Swift فتعطيك optional (قيمة اختيارية)، وتُلزمك بالتعامل مع حالة الغياب قبل أن يُترجَم الكود.

ما هو الـ optional

Swift
import Foundation

let typed = "12.50"
let amount = Decimal(string: typed)   // amount is Decimal?, not Decimal

علامة الاستفهام في Decimal? تعني «قيمة Decimal، أو لا شيء». و«لا شيء» تُكتب nil. في الداخل، الـ optional هو enum له حالتان، .some(value) و.none، ولهذا يمكنك أن تتخيّله صندوقًا إما ممتلئًا أو فارغًا.

تعيد Decimal(string:) قيمة Decimal اختيارية. مع "12.50" يحمل الصندوق 12.5؛ ومع نص فارغ يكون الصندوق فارغًا، أي nil. الفكّ يفتح الصندوق. Decimal(string:) .some(12.5) "12.50" nil "" أو "twelve" افتحه عالِجه
الـ optional صندوق إما ممتلئ أو فارغ

الـ optional ليس هو القيمة التي في داخله. جرّب amount + 5 وسيوقفك الـ compiler بالرسالة “value of optional type ‘Decimal?’ must be unwrapped to a value of type ‘Decimal’”. هذا الخطأ هو الميزة نفسها. كل مكان قد تغيب فيه القيمة مُعلَّم في النوع، ولن تسمح لك Swift بأن تنساه.

if let: استخدم القيمة في فرع واحد

يفحص if let الصندوق، وحين يكون ممتلئًا يعطيك القيمة باسم جديد غير optional:

Swift
if let amount = Decimal(string: typed) {
    print("Adding \(amount)")          // amount is a Decimal here
} else {
    print("That isn't a number")
}

حين يطابق الاسم الجديد اسم optional موجود، تسمح لك Swift 5.7 وما بعدها بحذف الطرف الأيمن: if let amount { … }. الاسم الجديد amount لا يوجد إلا داخل الأقواس، وهذا مثالي حين تهمّك القيمة في فرع صغير واحد.

guard let: اخرج مبكرًا وأبقِ المسار السعيد مستويًا

في أغلب الأحيان، القيمة الغائبة تعني «توقّف هنا». وguard let يقول ذلك بالضبط. هذه هي الدالة التي يستخدمها Pocket Budget للتحقّق من حقل المبلغ:

AmountParser.swift
import Foundation

func parseAmount(_ text: String) -> Decimal? {
    let trimmed = text.trimmingCharacters(in: .whitespaces)
    guard let amount = Decimal(string: trimmed), amount > 0 else {
        return nil
    }
    return amount
}

print(parseAmount("12.50") as Any)   // Optional(12.5)
print(parseAmount("") as Any)        // nil
print(parseAmount("-3") as Any)      // nil

يجب أن تغادر كتلة else النطاق الحالي باستخدام return أو throw أو break أو continue، والـ compiler يفرض ذلك. وفي المقابل، يبقى amount متاحًا كقيمة Decimal عادية حتى نهاية الدالة. الفاصلة تضيف شرطًا ثانيًا، فيرفض guard واحد الحالتين معًا: «ليس رقمًا» و«صفر أو سالب». الكود المكتوب بهذه الطريقة يُقرأ من الأعلى إلى الأسفل: الفحوصات أولًا، ثم العمل الحقيقي، بلا هرم من جمل if المتداخلة.

?? والـ optional chaining

أحيانًا يكون للقيمة الغائبة بديل واضح. عامل الدمج مع nil‏ ?? يقدّمه لك:

Swift
let note: String? = nil
let label = note ?? "No note"        // "No note"
let noteLength = note?.count ?? 0    // 0

note?.count هو optional chaining (السلسلة الاختيارية): إذا كان note يساوي nil، يصبح التعبير كله nil بدل أن يتعطّل التطبيق، ثم يحوّل ?? ذلك إلى 0. يمكن أن تكون السلاسل طويلة (expense?.category?.name)، وتتوقّف عند أول nil.

والـ optionals قرار تصميمي أيضًا، لا مجرّد شيء تعطيك إيّاه الدوال. في Pocket Budget، لكل مصروف عنوان ومبلغ دائمًا، فهما String وDecimal عاديان. أما الملاحظة فاختيارية فعلًا، لذلك نوعها String?. قاوم إغراء جعل الأشياء optional «من باب الاحتياط»: كل ? تضيفها هي سؤال على كل من يقرأ تلك القيمة أن يجيب عنه. اجعل الخاصية optional فقط حين يكون «لا قيمة» حالة حقيقية لها معنى.

الفكّ القسري

هناك طريقة أخرى لفتح الصندوق: !.

Swift
let amount = Decimal(string: typed)!

إذا كانت typed تساوي “12.50”، يعمل هذا. وإذا كان الحقل فارغًا، يموت تطبيقك بالرسالة “Fatal error: Unexpectedly found nil while unwrapping an Optional value”. لا رسالة خطأ للمستخدم، ولا فرصة للتعافي.

للعامل ! استخدامات مشروعة، مثل URL(string: "https://example.com")! المبني من قيمة كتبتها بنفسك. هنا، nil تعني خطأً إملائيًا في كودك، والتعطّل أثناء التطوير هو أسرع طريقة لاكتشافه. كن مستعدًا للدفاع عن كل ! أثناء مراجعة الكود.

مع الـ optionals صار بإمكانك التعامل مع قيمة واحدة غائبة بأمان. بعد ذلك ستخزّن قيمًا كثيرة دفعة واحدة في المصفوفات والقواميس والمجموعات، وتتنقّل بينها بحلقات التكرار وswitch.

أهم النقاط

  • الـ optional مثل Decimal? إما أن يحمل قيمة أو يحمل nil، ولن تسمح لك Swift باستخدامه كقيمة عادية حتى تفكّه.
  • استخدم guard let لفكّ القيمة في أعلى الدالة والخروج مبكرًا، فيعمل باقي الدالة بقيمة حقيقية.
  • استخدم if let حين تحتاج القيمة داخل فرع واحد فقط، و?? حين توجد قيمة افتراضية منطقية.
  • الفكّ القسري (!) يُسقط التطبيق في اللحظة التي تكون فيها القيمة nil؛ اعتبر كل ! ادّعاءً يجب أن تستطيع إثباته.

قراءات إضافية

تحقّق من فهمك

4 أسئلة

السؤال 1 من 4

لديك let note: String? = nil. ما قيمة note?.count ?? 0؟

السؤال 2 من 4

داخل دالة، بعد guard let amount = parseAmount(text) else { return }، ما الصحيح بشأن amount في السطر التالي؟

السؤال 3 من 4

متى يكون الفكّ القسري مثل URL(string: "https://example.com")! مقبولًا؟

السؤال 4 من 4

أيّ سطر يُترجَم حين يكون limit من النوع Int?؟

تدريب

اكتشف الخطأ

اعثر على سبب العطل

هذه النسخة المبكرة من منطق إضافة مصروف في Pocket Budget تعمل في كل عرض تجريبي، ثم تتعطّل عند مستخدم حقيقي. اختر السطر الذي يسبّب العطل.

حدّد السطر أو الأسطر التي تظنها خاطئة، ثم تحقّق.