الوحدة 1 · لغة Swift
المجموعات والتحكّم في التدفّق
17 دقراءة 4 داختر بحكمة
الدرس في لمحة
خزّن المصاريف في المصفوفات والقواميس والمجموعات، وتنقّل بينها بحلقة for-in، واتّخذ القرارات بجملة switch شاملة يفحصها الـ compiler عنك.
- 1المصفوفات: مرتّبة، مفهرسة، محدّدة النوع
- 2القواميس: بحث قد لا يجد شيئًا
- 3المجموعات: قيم فريدة
- 4اختيار المجموعة المناسبة
- 5switch: شاملة بالتصميم
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
المصروف الواحد قيمة. ومصاريف شهر كامل مجموعة (collection). يحتاج Pocket Budget إلى ثلاثة أشكال من المجموعات: قائمة مرتّبة بالمبالغ، وجدول بحث من التصنيف إلى الحدّ الشهري، وكيس من الوسوم الفريدة. تعطيك Swift هذه الثلاثة بالضبط، وكلها محدّدة النوع، فلا يمكن لمصفوفة مبالغ أن تحتوي String بالخطأ.
المصفوفات: مرتّبة، مفهرسة، محدّدة النوع
import Foundation
var amounts: [Decimal] = [12.5, 4.25, 60]
amounts.append(18)
print(amounts.count) // 4
print(amounts[0]) // 12.5
print(amounts.first as Any) // Optional(12.5)تُقرأ [Decimal] «مصفوفة من Decimal». الفهارس تبدأ من 0. لاحظ الفرق بين amounts[0] وamounts.first: الوصول بالفهرس يثق بك ويُسقط التطبيق مع “Index out of range” إذا كانت المصفوفة فارغة، بينما first يعيد optional ويتيح لك التعامل مع الحالة الفارغة بالأدوات التي تعلّمتها في الدرس السابق.
في أغلب الأحيان لا تحتاج إلى الفهارس أصلًا. حلقة for-in تزور كل عنصر:
var total: Decimal = 0
for amount in amounts {
total += amount
}
print(total) // 94.75
for (index, amount) in amounts.enumerated() {
print("\(index + 1). \(amount)")
}تعطيك enumerated() أزواجًا من الموضع والقيمة، وهذه هي الطريقة الآمنة لترقيم الصفوف. والنطاقات تعمل مع for-in أيضًا، بنوعين:
for day in 1...30 { } // closed range: 1 through 30
for i in 0..<amounts.count { } // half-open: stops before countالنطاق المغلق يشمل حدّه الأعلى؛ أما النطاق نصف المفتوح فيتوقّف قبله بواحد، وهذا بالضبط ما تحتاجه فهارس المصفوفات.
القواميس: بحث قد لا يجد شيئًا
القاموس (dictionary) يربط مفاتيح بقيم. يحتفظ Pocket Budget بحدّ شهري لكل تصنيف:
let limits: [String: Decimal] = ["Food": 300, "Transport": 120]
var spent: [String: Decimal] = ["Food": 280]
let foodLimit = limits["Food"] // Decimal?, Optional(300)
let funLimit = limits["Fun"] ?? 0 // Decimal, 0
spent["Fun", default: 0] += 15 // creates "Fun": 15كل بحث يعيد optional، لأن المفتاح قد لا يكون موجودًا. إنها فكرة الأمان نفسها مرة أخرى، لكنها هذه المرة مبنية داخل المجموعة. والصيغة default: هي الطريقة المعتادة لحساب المجاميع التراكمية: تقرأ القيمة الموجودة أو تبدأ من القيمة الافتراضية التي حدّدتها، ثم تكتب النتيجة مرة أخرى.
المجموعات: قيم فريدة
var tags: Set<String> = ["work", "travel"]
tags.insert("work") // already there, nothing changes
print(tags.count) // 2
print(tags.contains("travel")) // trueالمجموعة (Set) لا ترتيب فيها ولا تكرار، ويبقى contains سريعًا مهما كثرت عناصرها. استخدمها للوسوم، والفلاتر المختارة، وفحوصات «هل رأيت هذا المعرّف من قبل؟».
اختيار المجموعة المناسبة
| تحتاج إلى | استخدم | مثال من Pocket Budget |
|---|---|---|
| ترتيب، والتكرار مسموح، والوصول بالموضع | مصفوفة (Array) | مصاريف هذا الشهر، الأحدث أولًا |
| البحث عن قيمة بمفتاح | قاموس (Dictionary) | الحدّ الشهري لكل تصنيف |
| العضوية والتفرّد، والترتيب لا يهم | مجموعة (Set) | الوسوم على مصروف واحد |
إن تردّدت، فابدأ بمصفوفة. إنها المجموعة التي تعرضها قوائم SwiftUI، وأسهلها قراءةً في الـ debugger. انتقل إلى القاموس حين تجد نفسك تبحث في مصفوفة عن «العنصر الذي يحمل هذا الاسم»، وإلى المجموعة حين تجد نفسك تفحص التكرار قبل كل إضافة.
المجموعات الثلاث كلها قيم، لا كائنات مشتركة. var copy = amounts تعطيك مصفوفة مستقلّة: الإضافة إلى copy لا تمسّ amounts. لهذا السلوك اسم، سلوك القيمة (value semantics)، وله درس خاص في القسم التالي لأنه يحدّد طريقة تصميمك لكل نوع في نموذج بياناتك.
switch: شاملة بالتصميم
جملة switch في Swift أكثر بكثير من سلسلة فحوصات مساواة. فهي تطابق النطاقات، وتربط القيم، وتضيف الشروط، والـ compiler يصرّ على معالجة كل قيمة ممكنة. هكذا يختار Pocket Budget رسالة الحالة بناءً على النسبة المستهلكة من الميزانية:
func status(percentUsed: Int) -> String {
switch percentUsed {
case ..<0:
return "Check your numbers"
case 0..<50:
return "Plenty left"
case 50..<90:
return "On track"
case 90...100:
return "Nearly at your limit"
default:
return "Over budget"
}
}
print(status(percentUsed: 95)) // Nearly at your limitهناك فرقان عن لغات عائلة C. أولًا، لا يوجد break ولا انتقال عرضي إلى الحالة التالية: متى طابقت حالة، انتهت الـ switch. ثانيًا، احذف default وستفشل الترجمة مع “switch must be exhaustive”، لأن النوع Int له قيم لا تغطّيها حالاتك. يبدو هذا الخطأ مزعجًا مع الأعداد الصحيحة. لكن مع الـ enums، في القسم التالي، يصبح أنفع خطأ في اللغة: أضف حالة جديدة إلى نموذجك، وسيعرض لك الـ compiler كل switch نسيت تحديثها.
يمكن للحالات أيضًا أن تربط القيمة المطابَقة وتختبرها بـ where:
let used: Decimal = 15
let limit: Decimal = 10
switch used {
case 0:
print("Nothing spent yet")
case let amount where amount > limit:
print("Over by \(amount - limit)")
default:
print("Within budget")
}وفي Swift 5.9 صار ممكنًا استخدام switch وif كتعبيرات، فتعمل الصيغة let label = switch percentUsed { … } حين تنتج كل حالة قيمة واحدة. استخدم الصيغة الأوضح قراءةً؛ قاعدة الشمول واحدة في الحالتين.
صار بإمكانك الآن أن تحمل بيانات شهر كامل وتتّخذ قرارات بشأنها. حلقات التكرار في هذا الدرس تعمل، لكنها مكتوبة بالطريقة الطويلة. بعد ذلك ستجمع المنطق في دوال، وتستبدل معظم هذه الحلقات بـ closures مثل filter وmap وreduce.
أهم النقاط
- المصفوفة مرتّبة وفهارسها تبدأ من 0؛ والقراءة بعد نهايتها تُسقط التطبيق، لذلك فضّل
firstوlastوfor-inعلى الفهارس اليدوية. - البحث في القاموس يعيد دائمًا optional، لأن المفتاح قد لا يكون موجودًا؛ والصيغة
dict[key, default: 0]تعطيك قيمة بديلة. - المجموعة (Set) تخزّن قيمًا فريدة، وفحص
containsفيها سريع، ولا ترتيب لعناصرها. - جملة
switchفي Swift يجب أن تغطّي كل قيمة ممكنة، ولا تنتقل ضمنيًا إلى الحالة التالية، ويمكنها مطابقة النطاقات وشروطwhere.