الوحدة 1 · لغة Swift
الـ optionals بلا أعطال
16 دقراءة 4 داكتشف الخطأ
الدرس في لمحة
اقرأ أنواع الـ optional، وفكّها بأمان باستخدام if let وguard let و??، واشرح لماذا يُعدّ الفكّ القسري عطلًا ينتظر لحظته.
- 1ما هو الـ optional
- 2if let: استخدم القيمة في فرع واحد
- 3guard let: اخرج مبكرًا وأبقِ المسار السعيد مستويًا
- 4?? والـ optional chaining
- 5الفكّ القسري
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
ينقر المستخدم على Add Expense، ويكتب “12.50” في حقل المبلغ، ثم ينقر Save. يستقبل كودك String. وتحويلها إلى رقم قد يفشل: ربما الحقل فارغ، أو ربما كتب المستخدم “twelve”. كثير من اللغات تعطيك في هذه الحالة NaN أو صفرًا أو استثناءً. أما Swift فتعطيك optional (قيمة اختيارية)، وتُلزمك بالتعامل مع حالة الغياب قبل أن يُترجَم الكود.
ما هو الـ optional
import Foundation
let typed = "12.50"
let amount = Decimal(string: typed) // amount is Decimal?, not Decimalعلامة الاستفهام في Decimal? تعني «قيمة Decimal، أو لا شيء». و«لا شيء» تُكتب nil. في الداخل، الـ optional هو enum له حالتان، .some(value) و.none، ولهذا يمكنك أن تتخيّله صندوقًا إما ممتلئًا أو فارغًا.
الـ optional ليس هو القيمة التي في داخله. جرّب amount + 5 وسيوقفك الـ compiler بالرسالة “value of optional type ‘Decimal?’ must be unwrapped to a value of type ‘Decimal’”. هذا الخطأ هو الميزة نفسها. كل مكان قد تغيب فيه القيمة مُعلَّم في النوع، ولن تسمح لك Swift بأن تنساه.
if let: استخدم القيمة في فرع واحد
يفحص if let الصندوق، وحين يكون ممتلئًا يعطيك القيمة باسم جديد غير optional:
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 للتحقّق من حقل المبلغ:
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 ?? يقدّمه لك:
let note: String? = nil
let label = note ?? "No note" // "No note"
let noteLength = note?.count ?? 0 // 0note?.count هو optional chaining (السلسلة الاختيارية): إذا كان note يساوي nil، يصبح التعبير كله nil بدل أن يتعطّل التطبيق، ثم يحوّل ?? ذلك إلى 0. يمكن أن تكون السلاسل طويلة (expense?.category?.name)، وتتوقّف عند أول nil.
والـ optionals قرار تصميمي أيضًا، لا مجرّد شيء تعطيك إيّاه الدوال. في Pocket Budget، لكل مصروف عنوان ومبلغ دائمًا، فهما String وDecimal عاديان. أما الملاحظة فاختيارية فعلًا، لذلك نوعها String?. قاوم إغراء جعل الأشياء optional «من باب الاحتياط»: كل ? تضيفها هي سؤال على كل من يقرأ تلك القيمة أن يجيب عنه. اجعل الخاصية optional فقط حين يكون «لا قيمة» حالة حقيقية لها معنى.
الفكّ القسري
هناك طريقة أخرى لفتح الصندوق: !.
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؛ اعتبر كل!ادّعاءً يجب أن تستطيع إثباته.