Closures
Closures
Closures درس مجاني في Swift Academy على CoddyKit. هذا هو الدرس 1 من أصل 1. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Swift Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Swift Academy 1 دروس في المجموع.
مقدمة
مرحبًا،
الإغلاقات كتل مستقلة بذاتها من التعليمات البرمجية. ويمكن تمرير الإغلاق إلى الشفرة كمعامل، أو استخدامه داخل الشفرة.
الإغلاق نوع من الدوال، ويمكن استخدامه بتمريره داخل الشفرة. وهو يشبه lambdas في لغات البرمجة الأخرى.
إدارة الذاكرة
من أهم النقاط أن الإغلاق يستطيع تخزين قيم الثوابت والمتغيرات الموجودة في السياق الذي عُرّف فيه.
تُعرف عملية التخزين هذه باسم closing-over.
تتولى Swift إدارة الذاكرة نيابةً عنا أثناء عملية التخزين هذه.
أنواع الإغلاقات
توجد أنواع مختلفة من الإغلاقات.
- الدوال العامة هي إغلاقات مُسمّاة، ولا تحتفظ هذه الدوال بأي قيمة.
- الدوال المتداخلة يمكنها الاحتفاظ بقيم الاسم والدالة المحيطة بها.
- أما تعبير الإغلاق فهو إغلاق مجهول، ويمكنه تخزين بيانات من السياق المحيط به باستخدام صيغة بسيطة.
صيغة الإغلاق
صُممت صيغة الإغلاقات لتكون مبسطة قدر الإمكان. ومن أهم جوانب هذا التبسيط ما يلي.
- يمكن استنتاج المعاملات في الإغلاقات.
- يمكن استنتاج قيم الإرجاع.
- يمكن كتابة إغلاق ذي قيمة إرجاع في سطر واحد.
- يمكن التعبير عن أسماء الوسائط باستخدام اختصارات مثل $ 0 أو $ 1.
- يمكن استخدام صيغة الإغلاق اللاحق.
معلومة
تعني تعبيرات الإغلاق كتابة الإغلاقات مضمنة، مع التركيز على الصيغة والبساطة.
صيغة الإغلاق
يوضح نموذج الشفرة أدناه صيغة الإغلاق.
{ (parameters) -> return type in
statements
}
func backward(_ s1: String, _ s2: String) -> Bool {
return s1 > s2
}
//If we didn't use closures, our code structure would be as follows.
var reversedNames = names.sorted(by: backward)
صيغة الإغلاق
ومع ذلك، لا نحتاج مع الإغلاق إلى تعريف إغلاق عام مُسمّى من هذا النوع من دون داعٍ.
يكفي تعريف تعبير إغلاق بسيط في المعامل. دعونا نلقي نظرة على المثال.
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
let reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
print(reversedNames)
///sorted method takes closure as parameter.الكلمة المفتاحية in
يُفهم من الكلمة المفتاحية in أن جسم الإغلاق يبدأ.
قبل الكلمة المفتاحية in، يجب تعريف المعاملات التي يستقبلها الإغلاق ونوع الإرجاع.
استنتاج الأنواع من السياق
استنتاج الأنواع من السياق
في المثال أعلاه، يمكن فهم تعبير الإغلاق باستنتاج المعاملات ونوع الإرجاع، لأن الأسلوب sorted يُستخدم مع إغلاق.
لذلك، لا نحتاج إلى تعريفهما بشكل إضافي.
في هذا المثال، سيُفهم أن المعاملات التي يستقبلها الإغلاق من النوع String، لأن القيم الموجودة في المصفوفة names هي من النوع String.
وبالمثل، سيحتاج الإغلاق المعني إلى النوع Bool لإجراء الترتيب، ولذلك سيُستنتج نوع الإرجاع على أنه Bool.
مثال
سيؤدي ذلك إلى تبسيط الاستخدام كما يلي.
يمكن أيضًا حذف السهم من الشفرة من خلال استنتاج نوع الإرجاع.
let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]
let reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in
return s1 > s2
})
print(reversedNames)الصيغة المختصرة
يُفضَّل استخدام الصيغة الطويلة بدلًا من الصيغة المختصرة لتجنب إرباك قارئي الشفرة.
ويمكن اختصار الصيغة أعلاه أكثر.
يمكن اختصار قيمة الإرجاع في تعبير الإغلاق بحذف الكلمة المفتاحية return.
/*
In this example, the return keyword has been extracted.
It already has single expression.
There is no need to express it as an extra with return.
*/
var names = ["Coddy", "Kit"]
let reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )
print(reversedNames)مثال
حُذفت الكلمة المفتاحية return في هذا المثال.
يوجد تعبير واحد بالفعل، لذلك لا حاجة إلى كتابته بشكل إضافي باستخدام return.
reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )الأسماء المختصرة
يمكن اختصار الصيغة أكثر. أوضحنا في المثال أعلاه أن الإغلاق يستقبل معاملين، هما s1 وs2، وقد استُنتج نوعاهما.
يمكننا الآن تجنب عناء كتابة هذه المعاملات وتعريفها باستخدام الاختصارات.
وبما أنه لا يوجد تعريف للمعاملات أو تمرير لقيمة الإرجاع، يمكن حذف الكلمة المفتاحية in أيضًا.
let names = ["Coddy", "Kit"]
let reversedNames = names.sorted(by: { $0 > $1 } )
print(reversedNames)
// In this example $0 will shortcut the first parameter and $1 will shortcut the second parameter.اختصار المثال
var names = ["Coddy", "Kit"]
let reversedNames = names.sorted(by: >)
print(reversedNames)
// Greater than operator takes two parameters and the rotation type is squat.الإغلاق اللاحق
الإغلاق اللاحق
إذا كان الإغلاق المراد تمريره إلى دالة هو الوسيطة الأخيرة للدالة، وكان تعبير الإغلاق طويلًا، فقد يكون من المفيد كتابته على شكل عبارة إغلاق لاحق.
يمكننا استخدام الإغلاق اللاحق كتسهيل نحوي، إذ يُستخدم لزيادة وضوح الشفرة.
يكون المعامل الأخير في معظم أساليب iOS إغلاقًا.
المعامل الأخير
على سبيل المثال، المعامل الأخير في هذا المثال هو إغلاق.
class Animator {
class func animate(withDuration duration: Double, animations: () -> Void) {
animations()
}
}
class MyViewController {
func start() {
// With the traditional method, we can call this fund as follows.
Animator.animate(withDuration: 1, animations: { [unowned self] in
print("red")
})
}
}
MyViewController().start()بما أن الشفرة أعلاه أقل وضوحًا، يمكن كتابتها كما يلي
بما أن الشفرة أعلاه أقل وضوحًا، يمكن كتابتها كما يلي.
import Foundation
Thread.sleep(forTimeInterval: 1)
print("Background color changed to red")مثال
عند استخدام صيغة الإغلاق اللاحق، لا نحتاج إلى كتابة تسمية الوسيطة.
وذلك لأن الإغلاقات أصبحت الآن جزءًا من استدعاء الدالة.
func someFunctionThatTakesAClosure(closure: () -> Void) {
// function body goes here
}
// Here's how you call this function without using a trailing closure:
someFunctionThatTakesAClosure(closure: {
// closure's body goes here
})
// Here's how you call this function with a trailing closure instead:
someFunctionThatTakesAClosure() {
// trailing closure's body goes here
}الصيغة
يمكننا في الواقع اعتبار صيغة الإغلاق اللاحق تسهيلًا نحويًا، فهي تجعل الشفرة أكثر قابلية للقراءة.
/*
In the example below, animations
are expressed as closure.
*/
public class func animate(withDuration: TimeInterval, animations: () -> Void)
// In traditional usage, we need to make a call like the following.
UIView.animate(withDuration: 1, animations: { [unowned self] in
self.view.backgroundColor = UIColor.red
})
/*
In this way, both the readability of the code
increases and we avoid the use of extra brackets.
If it is as above and the last parameter of the method
is closure, Swift allows to make a call like the one below.
*/
UIView.animate(withDuration: 1) { [unowned self] in
self.view.backgroundColor = UIColor.red
}يمكن استخدام الشفرة أدناه لاختبار الشفرة
يمكن استخدام الشفرة أدناه لاختبار الشفرة.
func greetThenRunClosure(name: String, closure: () -> ()) {
print("Hello, \(name)!")
closure()
}
greetThenRunClosure(name: "Paul") {
print("The closure was run")
}التقاط القيم في الإغلاق
التقاط القيم في الإغلاق
يمكن للإغلاق الاحتفاظ بقيم المتغيرات التي تحيط ببيئته. بل يمكنه تغيير قيم هذه المتغيرات والوصول إليها من داخل جسمه.
وذلك حتى لو لم تكن هذه المتغيرات موجودة في النطاق الأصلي الذي عُرّفت فيه.
متابعة
بوجه عام، يمكن لتعبيرات الإغلاق الاحتفاظ بهذه البيانات داخل الدوال المتداخلة، كما يمكنها تخزين بيانات الدالة الخارجية.
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTen = makeIncrementer(forIncrement: 10)
print(incrementByTen())
// returns a value of 10
print(incrementByTen())
// returns a value of 20
print(incrementByTen())
// returns a value of 30متابعة
إذا أسندتم إغلاقًا إلى خاصية في فئة، فسيبدأ في الاحتفاظ بمرجع قوي إلى مثيل هذه الفئة.
وبذلك ستنشأ دورة مراجع قوية بين الإغلاق والمثيل.
لذلك ينبغي الانتباه جيدًا إلى تسرب الذاكرة.
تحذير
الإغلاق والدوال نوعان مرجعيان.
@escaping
قد نحتاج إلى استخدام ذلك في مواضع كثيرة يُستخدم فيها الإغلاق بشكل غير متزامن.
في الإصدارات الأولى من Swift (1 و2)، كانت الإغلاقات في معاملات Swift من النوع escaping، ثم أصبحت من النوع non-escaping. وإذا احتجنا إلى تمرير إغلاق non-escaping، كان علينا تحديد ذلك صراحةً.
في الإصدارات اللاحقة، جُعلت معاملات الإغلاق من النوع non-escaping للاستفادة من الذاكرة.
والآن، إذا أردنا تعريف إغلاق من النوع escaping، فعلينا استخدام الكلمة المفتاحية @escaping.
الكلمة المفتاحية @ESCAPING
الكلمة المفتاحية @escaping
إذا أردنا أن نتمكن من استدعاء الإغلاقات المسندة إلى دالة بعد عودة الدالة، فعلينا وسمها بالكلمة المفتاحية @escaping.
الإغلاقات التي تُمرَّر إلى الدوال تكون افتراضيًا من النوع المرجعي، لكنها لا تزيد عدد المراجع؛ فعند استدعائها ينتهي عمر هذا الإغلاق، ولا حاجة إلى زيادة عدد المراجع بشكل إضافي.
ومع ذلك، إذا احتجنا إلى الإغلاق الذي نمرره كمعامل بعد استدعاء الدالة، فقد نحتاج إلى التصريح عنه باستخدام الكلمة المفتاحية @escaping.
nonEscaping
لنرَ أولًا كيفية عمل الإغلاق non-escaping.
عند تمرير هذا الإغلاق إلى الدالة كمعامل، يُنفَّذ الإغلاق داخل جسم الدالة، ثم تعود الدالة إلى المترجم.
وبعد عودة الدالة، أي بعد اكتمال التنفيذ، يخرج الإغلاق من نطاقه ويُحرَّر من الذاكرة.
استخدام إغلاق escaping
استخدام إغلاق escaping
عند تمرير دالة إغلاق كوسيطة، يستمر الاحتفاظ بالإغلاق حتى بعد عودة دالة الإغلاق واكتمال التنفيذ.
ويشغل الإغلاق مساحة في الذاكرة حتى يُنفَّذ. وتوجد أنواع متعددة من الإغلاقات escaping.
تهانينا
تهانينا 🎉
لقد وصلنا إلى نهاية درسنا.
نراكم في الدرس التالي 😎

الأسئلة الشائعة
هل درس «Closures» مجاني؟
نعم — نص درس «Closures» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Swift Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Swift Academy 1 دروس في المجموع.
ماذا ستتعلم في «Closures»؟
Closures تتمرن على Swift Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Swift Academy؟
لا تُشترط خبرة سابقة. Swift Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 1.
كم من الوقت يستغرق درس «Closures»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Swift Academy هذا؟
نعم. كل درس في Swift Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.