تخطَّ إلى المحتوى

الهياكل والكلاسات في Swift: struct و class

الدرس 7 من 11 · Swift

في أغلب اللغات، الأداة الأساسية لتمثيل «الأشياء» هي الكلاس. أما Swift فتقدم أداتين: الهيكل (struct) والكلاس (class). يبدوان متشابهين جداً، لكن بينهما فرقاً جوهرياً سيغير طريقة تفكيرك في البيانات. وتوصي Apple نفسها باستخدام struct في أغلب الحالات. في هذا الدرس ستفهم الاثنين، ومتى تختار كلاً منهما.

struct Student {
let name: String
var age: Int
func introduce() {
print("أنا \(name) وعمري \(age) سنة")
}
}
var s1 = Student(name: "سارة", age: 14)
s1.introduce()
s1.age = 15
print(s1.age)
الناتج
أنا سارة وعمري 14 سنة
15
  • الخصائص (Properties): name و age. و let لما لا يتغير، و var لما يتغير.
  • الدوال (Methods): مثل introduce.
  • لاحظ أننا لم نكتب مُنشئاً! الهيكل يحصل على مُنشئ تلقائي (Memberwise Initializer) بأسماء خصائصه: Student(name:age:).
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.0

total خاصية محسوبة: لا تُخزَّن، بل تُحسب كلما قرأتها. وتقرؤها كأي خاصية عادية 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 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 = a
b.x = 5
print(a.x, b.x)
الناتج
1 5
class Box {
var value: Int
init(value: Int) {
self.value = value
}
}
let c = Box(value: 1)
let d = c
d.value = 5
print(c.value, d.value)
الناتج
5 5
  • الهيكل نوع قيمة (Value Type): var b = a تصنع نسخة مستقلة. تعديل b لا يمس a.
  • الكلاس نوع مرجعي (Reference Type): let d = c لا تنسخ، بل تجعل المتغيرين يشيران إلى نفس الكائن. تعديله من أحدهما يظهر في الآخر.

تخيّل الهيكل مثل ورقة مطبوعة: تعطي صديقك صورة منها، فيكتب عليها ما يشاء دون أن تتغير ورقتك. والكلاس مثل مستند مشترك على الإنترنت: تعطي صديقك الرابط، فأي تعديل منه تراه أنت فوراً.

لاحظ أيضاً: d معرّفة بـ let، ومع ذلك عدّلنا d.value! لأن let مع الكلاس تثبّت المرجع (إلى أي كائن يشير)، لا محتوى الكائن نفسه.

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 حتى لا تعيد الكتابة بالخطأ.
  • الهياكل لا تدعم الوراثة، وهذا من أسباب اختيار الكلاس أحياناً.
استخدم struct عندما استخدم class عندما
البيانات بسيطة: نقطة، منتج، رسالة تحتاج الوراثة
تريد نسخاً مستقلة آمنة تحتاج كائناً واحداً مشتركاً بين أجزاء التطبيق
أغلب الحالات، كما توصي Apple تتعامل مع مكتبات Apple القديمة المبنية على الكلاسات

القاعدة: ابدأ بـ struct، وانتقل إلى class فقط عندما تحتاج ما لا يقدمه الهيكل. وفي SwiftUI، كل شاشة تكتبها هي struct، مثل struct ContentView: View التي رأيتها في الدرس الأول.

  1. أنشئ struct Book فيه: العنوان والمؤلف وعدد الصفحات، وخاصية محسوبة isLong تُرجع true إذا تجاوزت الصفحات 300.
  2. أنشئ مصفوفة من 3 كتب، واطبع عناوين الطويلة منها باستخدام filter.
  3. أنشئ نسخة من كتاب في متغير جديد، وغيّر عنوانها، وتأكد أن الأصل لم يتغير.
  4. تحدٍّ: أنشئ class Shape بدالة area() تُرجع 0، وكلاسين Circle و Rectangle يرثان منها ويعيدان كتابة area.
  • تعديل خاصية في دالة struct بدون mutating: خطأ ترجمة. أضف mutating.
  • توقع أن نسخ الكلاس ينشئ نسخة مستقلة: الكلاس يُشارَك. استخدم struct إذا أردت نسخاً مستقلة.
  • نسيان override: خطأ ترجمة عند إعادة كتابة دالة موروثة.

سؤال

ماذا يطبع الكود: var a = Point(x: 1); var b = a; b.x = 9; print(a.x) إذا كان Point هيكلاً struct؟

سؤال

لماذا تحتاج دالة داخل struct الكلمة mutating؟

  • struct يحصل على مُنشئ تلقائي، و class يحتاج init تكتبه بنفسك.
  • الخصائص المحسوبة تُحسب عند القراءة، و mutating للدوال التي تعدّل الهيكل.
  • الهيكل نوع قيمة يُنسخ، والكلاس نوع مرجعي يُشارَك.
  • الوراثة بـ class Cat: Animal و override للكلاسات فقط، و private(set) لحماية البيانات.
  • ابدأ بـ struct كما توصي Apple.