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

الدوال في Swift: التسميات والمغلقات Closures

الدرس 5 من 11 · Swift

الدوال في Swift لها طابع خاص يجعل الكود يُقرأ كجملة إنجليزية: greet(person: "سارة") و move(from: start, to: end). هذا بفضل ميزة اسمها تسميات المعاملات (Argument Labels). في هذا الدرس ستكتب دوالك، وتتعلم هذه الميزة، ثم تتعرف على المغلقات (Closures) التي ستراها في كل مكان في SwiftUI.

func greet() {
print("أهلاً بك في Swift!")
}
func add(a: Int, b: Int) -> Int {
return a + b
}
greet()
print(add(a: 3, b: 4))
الناتج
أهلاً بك في Swift!
7
  • func: كلمة تعريف الدالة.
  • (a: Int, b: Int): المعاملات وأنواعها.
  • -> Int: السهم ثم نوع القيمة المرجعة. الدالة التي لا تُرجع شيئاً لا تحتاج سهماً.
  • عند الاستدعاء تكتب أسماء المعاملات: add(a: 3, b: 4).

وإذا كان جسم الدالة تعبيراً واحداً، يمكنك حذف return:

func square(_ x: Int) -> Int {
x * x
}

كل معامل في Swift له اسمان محتملان:

  • التسمية الخارجية (Argument Label): تُكتب عند الاستدعاء.
  • الاسم الداخلي (Parameter Name): يُستخدم داخل الدالة.
func greet(person name: String) -> String {
"أهلاً يا \(name)"
}
print(greet(person: "سارة"))
الناتج
أهلاً يا سارة

person تسمية خارجية تجعل الاستدعاء مقروءاً: greet(person: "سارة")، و name اسم داخلي واضح داخل الدالة.

وإذا كانت التسمية لا تضيف وضوحاً، أخفِها بالشرطة السفلية _:

print(square(5))
الناتج
25

بفضل _ في func square(_ x: Int) نكتب square(5) بدلاً من square(x: 5).

تخيّل التسميات مثل الأزرار المكتوب عليها في جهاز التحكم: «رفع الصوت» أوضح بكثير من زر بلا كتابة. لكن زر «تشغيل» واضح من شكله، فلا يحتاج كتابة.

func makeTea(sugar: Int = 2) -> String {
"شاي بـ \(sugar) ملاعق سكر"
}
print(makeTea())
print(makeTea(sugar: 0))
الناتج
شاي بـ 2 ملاعق سكر
شاي بـ 0 ملاعق سكر

inout: تعديل المتغير الأصلي

Section titled “inout: تعديل المتغير الأصلي”

المعاملات في Swift ثوابت داخل الدالة، فلا يمكن تغييرها. وإذا أردت أن تعدّل الدالة متغيراً من خارجها، استخدم inout، وضع & قبل المتغير عند الاستدعاء:

func addPoints(_ score: inout Int, bonus: Int) {
score += bonus
}
var myScore = 10
addPoints(&myScore, bonus: 5)
print(myScore)
الناتج
15

العلامة & تُذكّر كل من يقرأ الكود أن هذا المتغير قد يتغير داخل الدالة. لكن الأسلوب المفضل غالباً أن تُرجع الدالة القيمة الجديدة بدلاً من inout.

المغلق دالة بلا اسم، تكتبها مباشرة بين قوسين معقوفين وتمررها كقيمة:

let double = { (x: Int) -> Int in
x * 2
}
print(double(21))
الناتج
42

الكلمة in تفصل بين المعاملات والجسم. وأجمل استخدام للمغلقات مع المصفوفات:

let numbers = [1, 2, 3]
let doubled = numbers.map { $0 * 2 }
print(doubled)
الناتج
[2, 4, 6]
  • map: تطبق المغلق على كل عنصر وتُرجع مصفوفة جديدة.
  • $0: اختصار لـ المعامل الأول في المغلق، فلا تحتاج تسميته.
  • عندما يكون المغلق آخر معامل، يُكتب خارج القوسين، ويسمى Trailing Closure.

تبدو مختصرة جداً، لكنها مألوفة بسرعة. ولهذا الأسلوب أهمية كبيرة: واجهات SwiftUI كلها مبنية بالمغلقات، مثل:

Button("اضغط هنا") {
print("تم الضغط")
}

المغلق بعد Button(...) هو الكود الذي يُنفَّذ عند الضغط.

func priceAfterDiscount(_ price: Double, percent: Double = 10) -> Double {
price - price * percent / 100
}
print(priceAfterDiscount(200))
print(priceAfterDiscount(200, percent: 25))
الناتج المتوقع
180.0
150.0

تحدٍّ: اكتب دالة grade(for score: Int) -> String تستخدم switch بالمجالات، ثم استخدم map لتحويل مصفوفة درجات إلى مصفوفة تقديرات.

  • نسيان التسمية عند الاستدعاء: add(3, 4) خطأ إذا كانت الدالة add(a:b:). اكتب add(a: 3, b: 4)، أو استخدم _ في التعريف.
  • محاولة تغيير معامل داخل الدالة: المعاملات ثوابت. انسخها في var، أو استخدم inout.
  • نسيان & مع inout: خطأ ترجمة يطلب منك إضافتها.

سؤال

كيف تستدعي الدالة func move(from start: Int, to end: Int)؟

سؤال

ماذا يعني $0 داخل المغلق numbers.map { $0 + 1 }؟

  • func name(param: Type) -> ReturnType، وجسم التعبير الواحد لا يحتاج return.
  • التسميات الخارجية تجعل الاستدعاء كجملة مقروءة، و _ تخفيها.
  • القيم الافتراضية للمعاملات، و inout مع & لتعديل المتغير الأصلي.
  • المغلقات دوال بلا اسم { (x) -> T in ... }، و $0 اختصار للمعامل الأول.
  • المغلق الأخير يُكتب خارج القوسين، وهذا أساس كود SwiftUI.