الهياكل والكلاسات في Swift: struct و class
في أغلب اللغات، الأداة الأساسية لتمثيل «الأشياء» هي الكلاس. أما Swift فتقدم أداتين: الهيكل (struct) والكلاس (class). يبدوان متشابهين جداً، لكن بينهما فرقاً جوهرياً سيغير طريقة تفكيرك في البيانات. وتوصي Apple نفسها باستخدام struct في أغلب الحالات. في هذا الدرس ستفهم الاثنين، ومتى تختار كلاً منهما.
الهيكل (struct)
Section titled “الهيكل (struct)”struct Student { let name: String var age: Int
func introduce() { print("أنا \(name) وعمري \(age) سنة") }}
var s1 = Student(name: "سارة", age: 14)s1.introduce()s1.age = 15print(s1.age)أنا سارة وعمري 14 سنة15- الخصائص (Properties):
nameوage. وletلما لا يتغير، وvarلما يتغير. - الدوال (Methods): مثل
introduce. - لاحظ أننا لم نكتب مُنشئاً! الهيكل يحصل على مُنشئ تلقائي (Memberwise Initializer) بأسماء خصائصه:
Student(name:age:).
الخصائص المحسوبة
Section titled “الخصائص المحسوبة”struct Product { let name: String let price: Double var quantity: Int = 1
var total: Double { price * Double(quantity) }}
let p = Product(name: "كتاب", price: 60, quantity: 3)print(p.total)180.0total خاصية محسوبة: لا تُخزَّن، بل تُحسب كلما قرأتها. وتقرؤها كأي خاصية عادية p.total بدون أقواس. ولاحظ القيمة الافتراضية quantity: Int = 1، فيمكن الإنشاء بدونها: Product(name: "قلم", price: 5).
mutating: دوال تعدّل الهيكل
Section titled “mutating: دوال تعدّل الهيكل”دوال الهيكل لا تستطيع تعديل خصائصه افتراضياً. إذا احتجت ذلك، علّمها بـ mutating:
struct Counter { var count = 0
mutating func increment() { count += 1 }}
var counter = Counter()counter.increment()counter.increment()print(counter.count)2هذا الوضوح مقصود: بنظرة واحدة تعرف أي الدوال تغيّر البيانات وأيها تقرأ فقط.
الكلاس (class)
Section titled “الكلاس (class)”class BankAccount { let owner: String private(set) var balance = 0.0
init(owner: String) { self.owner = owner }
func deposit(_ amount: Double) { guard amount > 0 else { print("المبلغ يجب أن يكون أكبر من صفر") return } balance += amount }}
let account = BankAccount(owner: "منى")account.deposit(500)account.deposit(-20)print(account.balance)المبلغ يجب أن يكون أكبر من صفر500.0- الكلاس يحتاج مُنشئاً
initتكتبه بنفسك. self.owner: خاصية الكائن، للتمييز بينها وبين المعامل بنفس الاسم.private(set): الجميع يقرأbalance، لكن لا يعدّلها إلا الكلاس نفسه عبرdepositالتي تتحقق من المبلغ. هذا هو التغليف.- لاحظ استخدام
guardمن درس الشروط.
الفرق الجوهري: النسخ أم المشاركة؟
Section titled “الفرق الجوهري: النسخ أم المشاركة؟”هذه أهم فكرة في الدرس. انظر:
struct Point { var x: Int}
var a = Point(x: 1)var b = ab.x = 5print(a.x, b.x)1 5class Box { var value: Int init(value: Int) { self.value = value }}
let c = Box(value: 1)let d = cd.value = 5print(c.value, d.value)5 5- الهيكل نوع قيمة (Value Type):
var b = aتصنع نسخة مستقلة. تعديلbلا يمسa. - الكلاس نوع مرجعي (Reference Type):
let d = cلا تنسخ، بل تجعل المتغيرين يشيران إلى نفس الكائن. تعديله من أحدهما يظهر في الآخر.
تخيّل الهيكل مثل ورقة مطبوعة: تعطي صديقك صورة منها، فيكتب عليها ما يشاء دون أن تتغير ورقتك. والكلاس مثل مستند مشترك على الإنترنت: تعطي صديقك الرابط، فأي تعديل منه تراه أنت فوراً.
لاحظ أيضاً: d معرّفة بـ let، ومع ذلك عدّلنا d.value! لأن let مع الكلاس تثبّت المرجع (إلى أي كائن يشير)، لا محتوى الكائن نفسه.
الوراثة: للكلاسات فقط
Section titled “الوراثة: للكلاسات فقط”class Animal { let name: String
init(name: String) { self.name = name }
func speak() { print("\(name) يصدر صوتاً") }}
class Cat: Animal { override func speak() { print("\(name) يقول: مياو") }}
let animals: [Animal] = [Animal(name: "الحصان"), Cat(name: "مشمش")]for animal in animals { animal.speak()}الحصان يصدر صوتاًمشمش يقول: مياوclass Cat: Animal: القطة ترث من الحيوان، ومنه ترثnameوinitوspeak.override: إعادة كتابة دالة موروثة، وهي إجبارية في Swift حتى لا تعيد الكتابة بالخطأ.- الهياكل لا تدعم الوراثة، وهذا من أسباب اختيار الكلاس أحياناً.
متى أستخدم أيهما؟
Section titled “متى أستخدم أيهما؟”| استخدم struct عندما | استخدم class عندما |
|---|---|
| البيانات بسيطة: نقطة، منتج، رسالة | تحتاج الوراثة |
| تريد نسخاً مستقلة آمنة | تحتاج كائناً واحداً مشتركاً بين أجزاء التطبيق |
| أغلب الحالات، كما توصي Apple | تتعامل مع مكتبات Apple القديمة المبنية على الكلاسات |
القاعدة: ابدأ بـ struct، وانتقل إلى class فقط عندما تحتاج ما لا يقدمه الهيكل. وفي SwiftUI، كل شاشة تكتبها هي struct، مثل struct ContentView: View التي رأيتها في الدرس الأول.
جرّب بنفسك
Section titled “جرّب بنفسك”- أنشئ
struct Bookفيه: العنوان والمؤلف وعدد الصفحات، وخاصية محسوبةisLongتُرجعtrueإذا تجاوزت الصفحات 300. - أنشئ مصفوفة من 3 كتب، واطبع عناوين الطويلة منها باستخدام
filter. - أنشئ نسخة من كتاب في متغير جديد، وغيّر عنوانها، وتأكد أن الأصل لم يتغير.
- تحدٍّ: أنشئ
class Shapeبدالةarea()تُرجع 0، وكلاسينCircleوRectangleيرثان منها ويعيدان كتابةarea.
أخطاء شائعة
Section titled “أخطاء شائعة”- تعديل خاصية في دالة struct بدون mutating: خطأ ترجمة. أضف
mutating. - توقع أن نسخ الكلاس ينشئ نسخة مستقلة: الكلاس يُشارَك. استخدم
structإذا أردت نسخاً مستقلة. - نسيان override: خطأ ترجمة عند إعادة كتابة دالة موروثة.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ماذا يطبع الكود: var a = Point(x: 1); var b = a; b.x = 9; print(a.x) إذا كان Point هيكلاً struct؟
الهيكل نوع قيمة، فـ b نسخة مستقلة، وتعديلها لا يغيّر a.
سؤال
لماذا تحتاج دالة داخل struct الكلمة mutating؟
دوال الهيكل للقراءة افتراضياً، و mutating تعلن صراحة أن الدالة ستعدّل بياناته.
الخلاصة
Section titled “الخلاصة”structيحصل على مُنشئ تلقائي، وclassيحتاجinitتكتبه بنفسك.- الخصائص المحسوبة تُحسب عند القراءة، و
mutatingللدوال التي تعدّل الهيكل. - الهيكل نوع قيمة يُنسخ، والكلاس نوع مرجعي يُشارَك.
- الوراثة بـ
class Cat: Animalوoverrideللكلاسات فقط، وprivate(set)لحماية البيانات. - ابدأ بـ
structكما توصي Apple.