تعلم Swift من الصفر: الاغلاقات (Closures) |
إذا كنت قد عملت مسبقاً مع الحواجز في C/Objective-C أو مع الحواجز في Ruby، فإنك لن تجد صعوبة في استيعاب مفهوم الإغلاقات. تعد الإغلاقات حواجز تشغيل يمكنك عرضها في الكود الخاص بك.
في واقع الأمر، لقد عملنا مسبقاً مع الإغلاقات في الدرسين السابقين. هذا صحيح. تعد الدوال إغلاقات أيضاً. دعونا نبدأ بالأساسيات ونستعرض التركيب البنيوي للإغلاق.
1. ما هو الإغلاق؟
كما قلت مسبقاً، يعد الإغلاق حاجز تشغيل يمكنك عرضه في الكود الخاص بك. يمكنك عرض الإغلاق على أنه معاملة لدالة ما أو بامكانك تخزينه كخاصية كائن. لدى الإغلاقات عدة حالات استخدام.
يلمح الاسم إغلاق [closure] إلى واحدة من أهم ميزات الإغلاقات. يحصل الإغلاق على المتغيرات والثوابت في المكان الذي يعرف به. وهذا ما يتم الإشارة له بـ: (إغلاق) closing over هذه المتغيرات والثوابت. سنلق نظرة على القيمة المأخوذة مع تفصيل أكثر في نهاية هذا الدرس.
يلمح الاسم إغلاق [closure] إلى واحدة من أهم ميزات الإغلاقات. يحصل الإغلاق على المتغيرات والثوابت في المكان الذي يعرف به. وهذا ما يتم الإشارة له بـ: (إغلاق) closing over هذه المتغيرات والثوابت. سنلق نظرة على القيمة المأخوذة مع تفصيل أكثر في نهاية هذا الدرس.
المرونة
لقد تعلمت مسبقاً أن الدوال يمكن أن تكون فعالة ومرنة بشكل لا يصدق. ولأن الدوال عبارة عن إغلاقات، فإن الإغلاقات تعتبر مرنة مثلها تماماً. في هذا الدرس، ستكتشف إلى أي درجة هم مرنين وفعالين.
إدارة الذاكرة
تحوي لغة البرمجة C مفهوماً مشابهاً، الحواجز. إلا أن لدى الإغلاقات في Swift بعض الفوائد. إحدى الفوائد الرئيسية للإغلاقات في Swift هي أن إدارة الذاكرة هي الشيء الذي أنت، المطور، لا يجب أن تقلق حياله.
حتى استرجاع السلاسل، التي لا تعتبر غير شائعة في C/Objective-C، تتم معالجتها من قبل Swift وهذا ما سيقلل صعوبة إيجاد تسريبات أو إنهيارات الذاكرة التي تنتج عن مؤشرات غير صالحة للخدمة.
حتى استرجاع السلاسل، التي لا تعتبر غير شائعة في C/Objective-C، تتم معالجتها من قبل Swift وهذا ما سيقلل صعوبة إيجاد تسريبات أو إنهيارات الذاكرة التي تنتج عن مؤشرات غير صالحة للخدمة.
2. تركيبة الكود
لا تعتبر تركيبة كود الإغلاق صعبة وستذكرك الدوال العالمية المتداخلة، التي أخذناها مسبقاً ضمن هذه السلسلة. ألق نظرة على المثال التالي.
{(a: Int) -> Int in
return a + 1
}
أول ما ستلاحظه هو أن كامل الإغلاق ملفوف بقوسين معوجين. كما أن عوامل الإغلاق المتغيرة ملفوفة بقوسين على شكل هلال، مفصول عن نوع return بالرمز -> . يقبل الإغلاق الذي في الأعلى معاملة واحدة ،a، من نوع Int ويرجع Int . يبدأ محتوى الإغلاق بعد الكلمة الأساسية in
تبدو الإغلاقات المسماة، أعني الدوال المتداخلة والعالمية، مختلفة قليلاً. يوضح المثال التالي هذه الإختلافات.
func increment(a: Int) -> Int {
return a + 1
}
من أبرز هذه الإختلافات هي استخدام الكلمة الأساسية func ومكان العوامل المتغيرة ونوع return . يبدأ وينتهي الإغلاق بقوس معوج، وبذلك يلف كلاً من العوامل المتغيرة ونوع return ومحتوى الإغلاق. على الرغم من وجود هذه الإختلافات، تذكر أن كل دالة هي عبارة عن إغلاق. ومع ذلك فإن ليس كل إغلاق هو عبارة عن دالة.
3. الإغلاقات كعوامل متغيرة
تعد الإغلاقات فعالة ويوضح المثال التالي إلى أي مدى يمكن أن يكونوا مفيدين. في هذا المثال، نقوم بإبتكار نظام من الحالات. نقوم أيضاً باستدعاء دالة map إلى النظام من أجل إبتكار نظام جديد يحوي فقط على أول حرفين من كل حالة على شكل شريط مكتوب بأحرف كبيرة.
var states = ["California", "New York", "Texas", "Alaska"]
let abbreviatedStates = states.map({ (state: String) -> String in
return state.substringToIndex(advance(state.startIndex, 2)).uppercaseString
})
println(abbreviatedStates)
تعد دالة أو طريقة map شائعة الإستخدام في عدة لغات برمجة وعدة مكتبات مثل Ruby و PHP و jQuery في المثال الذي في الأعلى، تم استدعاء دالة map لنظام states، وبذلك تغيير محتواها، وإرجاعها كنظام جديد يحوي على قيم متغيرة.
نوع الاستدلال
لقد تعلمنا مسبقاً في هذه السلسلة أن Swift ذكي جداً. دعونا نرى ما مدى ذكائه. يعتبر نظام الحلات نظاماً من الأشرطة. ولأننا نستدعي دالة map إلى النظام، فإن Swift يعلم بأن معاملة state هو من نوع String . مما يعني أنه بإمكاننا حذف النوع كما هو ظاهر في المثال المحدث أسفلاً.
let abbreviations = states.map({ (state) -> String in
return state.substringToIndex(advance(state.startIndex, 2)).uppercaseString
})
هنالك بعض الأشياء الأخرى التي يمكننا حذفها في المثال، وهذه هي النتيجة في خط واحد.
let abbreviations = states.map({ state in state.substringToIndex(advance(state.startIndex,)).uppercaseString })
دعوني أوضح ما الذي حصل هناك. يمكن للمجمع أن يستدل أننا استرجعنا شريطاً من الإغلاق الذي عرضناها في دالة map، والذي يعني أنه ليس هنالك سبباً لنضمنه بتعريف الإغلاق. ولكن يمكننا فعل ذلك فقط إذا كان محتوى الإغلاق يتضمن تصريحاً واحداً. في تلك الحالة، يمكننا وضع ذلك التصريح على نفس الخط كتعريف مصطلح الإغلاق، كما هو موضح بالمثال في الأعلى. ولأنه لا يوجد نوع return في التعريف ولا رمز -> قبل نوع return، فإنه بإمكاننا حذف القوسين اللذان يغلقان عوامل الإغلاق المتغيرة.
أسماء البراهين (Argument) المختصرة
ومع ذلك فنحن لن نقف عند هذا الحد. يمكننا الإستفادة من أسماء البراهين المختصرة من أجل تبسيط مصطلح الإغلاق حتى أكثر. عند استخدام إغلاق مكتوب على نفس الخط، كما هو الحال في المثال السابق، فإنه بإمكاننا حذف قائمة العوامل المتغيرة، بما في ذلك الكلمة الأساسية in التي تفصل العوامل المتغيرة عن محتوى الإغلاق.
في محتوى الإغلاق، نقوم بتوجيه البراهين مستخدمين أسماء البراهين المختصرة التي يزودنا بها Swift. تم توجيه المعاملة الأولى عن طريق $0، والثاني عن طريق $1، إلخ.
في المثال المحدث أسفلاً، قمت بحذف قائمة العوامل المتغيرة والكلمة الأساسية in، واستبدلت معاملة state في محتوى الإغلاق باسم المعاملة المختصرة $0. والنتيجة هي الحصول على تصريح مختصر أكثر.
في محتوى الإغلاق، نقوم بتوجيه البراهين مستخدمين أسماء البراهين المختصرة التي يزودنا بها Swift. تم توجيه المعاملة الأولى عن طريق $0، والثاني عن طريق $1، إلخ.
في المثال المحدث أسفلاً، قمت بحذف قائمة العوامل المتغيرة والكلمة الأساسية in، واستبدلت معاملة state في محتوى الإغلاق باسم المعاملة المختصرة $0. والنتيجة هي الحصول على تصريح مختصر أكثر.
let abbreviations = states.map({ $0.substringToIndex(advance($0.startIndex, 2)).uppercaseString })
سحب الإغلاقات
تقوم لغة البرمجة Swift أيضاً بتعريف مفهوماً يعرف بسحب الإغلاقات. الفكرة بسيطة جداً. إذا قمت بعرض الإغلاق كدالة أخر معاملة، بإمكانك حينها أن تضع ذلك الإغلاق خارج أقواس الدالة. يوضح المثال التالي كيفية عمل ذلك.
let abbreviations = states.map() { $0.substringToIndex(advance($0.startIndex, 2)).uppercaseString }
في حال كانت معاملة الدالة الوحيد هو الإغلاق، فإنه يمكننا حتى حذف أقواس الدالة.
let abbreviations = states.map { $0.substringToIndex(advance($0.startIndex, 2)).uppercaseString }
لاحظ أن ذلك ينطبق أيضاً على الإغلاقات التي تحوي عدة تصريحات. في الحقيقة، هذا هو السبب الرئيسي لوجود إغلاقات مسحوبة في Swift. غالباً ما يفضل استخدام تركيبة كود الإغلاقات المسحوبة في حال كان الإغلاق طويلاً أو معقداً وكانت أخر معاملة لدالة ما.
let abbreviations = states.map { (state: String) -> String in
let newState = state.substringToIndex(advance(state.startIndex, \))
return newState.uppercaseString
}
4. أخذ القيم
عند استخدام الإغلاقات، غالباً ما ستجد نفسك تستخدم أو تتلاعب بالثوابت والمتغيرات المتواجدة حول مجال الإغلاق في محتوى الإغلاق. إن ذلك ممكن وغالباً ما يعرف بأخذ القيم. ذلك يعني ببساطة أنه يمكن للإغلاق أن يحصل على قيم الثوابت والمتغيرات من المجال الذي يتم تعريفه به. لتفهم أكثر هذا المفهوم، ألق نظرة على هذا المثال.
func changeCase(uppercase: Bool, strings: String...) -> [String] {
var newStrings = [String]()
func changeToUppercase() {
for s in strings { newStrings.append(s.uppercaseString)
}
}
func changeToLowerCase() {
for s in strings { newStrings.append(s.lowercaseString) }
}
if uppercase {
changeToUppercase()
} else {
changeToLowerCase()
} return newStrings
}
let uppercaseStates = changeCase(true, "Califorina", "New York")
let lowercaseStates = changeCase(false, "Califorina", "New York")
أنني متأكد أنك ستوافقني الرأي بأن هذا المثال معقد قليلاً، ولكنه يظهر بوضوح كيفية عمل أخذ القيم في Swift. يستطيع كلاً من دوال، changeToUppercase و changeToLowercase، الولوج إلى دالة البراهين الخارجية ،states، وكذلك متغير newStates المصرح عنه في الدالة الخارجية. دعني أشرح ما قد حصل للتوه.
قامت دالة changeCase بقبول boolean كأول معاملة لها وعامل variadic المتغير من نوع String كثاني عامل متغير. تعيد الدالة نظاماً من الأشرطة التي تتألف من أشرطة تم تمريرها إلى الدالة كمعاملة ثانية. في محتوى الدالة، نقوم بإبتكار نظاماً متعدداً من الأشرطة، newStrings، حيث نخزين الأشرطة المتغيرة.
تلتف الدوال المتداخلة حول الأشرطة التي تم تمريرها لدالة changeCase وتغير حالة كل شريط بذلك. كما يمكنك أن ترى، لدى هذه الدوال وصولاً مباشر للأشرطة التي تم تمريرها لدالة changeCase وكذلك نظام newStrings، الذي تم التصريح عنه في محتوى دالة changeCase.
نقوم بالتحقق من قيمة uppercase، استدعاء الدالة المناسبة، وإرجاع نظام newStrings. يوضح أخر سطرين في المثال كيفية عمل دالة changeCase
على الرغم من أننا عرضنا كيفية أخذ القيم مع الدوال، تذكر أن كل دالة هي عبارة عن إغلاق. وبكلمات أخرى، فإن نفس القواعد تنطبق على الإغلاقات غير المسماة.
الإغلاقات
تعتبر الدوال إغلاقات، لقد ذكرنا ذلك عدة مرات في هذا الدرس. هنالك ثلاثة أنواع للإغلاقات:
قامت دالة changeCase بقبول boolean كأول معاملة لها وعامل variadic المتغير من نوع String كثاني عامل متغير. تعيد الدالة نظاماً من الأشرطة التي تتألف من أشرطة تم تمريرها إلى الدالة كمعاملة ثانية. في محتوى الدالة، نقوم بإبتكار نظاماً متعدداً من الأشرطة، newStrings، حيث نخزين الأشرطة المتغيرة.
تلتف الدوال المتداخلة حول الأشرطة التي تم تمريرها لدالة changeCase وتغير حالة كل شريط بذلك. كما يمكنك أن ترى، لدى هذه الدوال وصولاً مباشر للأشرطة التي تم تمريرها لدالة changeCase وكذلك نظام newStrings، الذي تم التصريح عنه في محتوى دالة changeCase.
نقوم بالتحقق من قيمة uppercase، استدعاء الدالة المناسبة، وإرجاع نظام newStrings. يوضح أخر سطرين في المثال كيفية عمل دالة changeCase
على الرغم من أننا عرضنا كيفية أخذ القيم مع الدوال، تذكر أن كل دالة هي عبارة عن إغلاق. وبكلمات أخرى، فإن نفس القواعد تنطبق على الإغلاقات غير المسماة.
الإغلاقات
تعتبر الدوال إغلاقات، لقد ذكرنا ذلك عدة مرات في هذا الدرس. هنالك ثلاثة أنواع للإغلاقات:
- دوال عالمية
- دوال متداخلة
- تعابير إغلاق
لا تحصل الدوال العالمية، مثل دالة println في مكتبة Swift، على أية قيم. ولكن تستطيع الدوال المتداخلة الوصول والحصول على قيم الثوابت وقيم الدالة المعرفة بها.
يوضح المثال السابق هذا المفهوم.
تستطيع تعابير الإغلاق، والمعروفة أيضاً بالإغلاقات غير المسماة، الحصول على قيم الثوابت والمتغيرات الموجودة في المجال المعرفة به. يعتبر ذلك مشابهاً جداً لما يحدث بالدوال المتداخلة.
النسخ والإسناد
بإمكان الإغلاق الذي يحصل على قيمة متغير ما أن يقوم بتغيير قيمة ذلك المتغير. إن Swift ذكي كفاية ليعرف فيما إذا كان من الضروري نسخ وإسناد قيم الثوابت والمتغيرات التي يحصل عليها.
سيعتبر المطورون الذين يستخدمون Swift من جديد ولديهم خبرة قليلة بلغات البرمجة أن هذا الأسلوب من المسلمات. ولكن يمكننا أن نستفيد من فهم Swift لكيفية استخدام القيم المأخوذة في الإغلاق، وبالنتيجة، يمكنه معالجة إدارة الذكرة بالنسبة لنا.
سيعتبر المطورون الذين يستخدمون Swift من جديد ولديهم خبرة قليلة بلغات البرمجة أن هذا الأسلوب من المسلمات. ولكن يمكننا أن نستفيد من فهم Swift لكيفية استخدام القيم المأخوذة في الإغلاق، وبالنتيجة، يمكنه معالجة إدارة الذكرة بالنسبة لنا.
الخلاصة
تعد الإغلاقات مفهوماً هاماً وغالباً ما ستستخدمه في Swift. تمكنك الإغلاقات من كتابة كودات مرنة وديناميكية يسهل فهمها وكتابتها. في الدرس التالي، سنستطلع البرمجة كائنية التوجه في Swift، بدأ بالكائنات، البنى ومن ثم الأصناف.
ليست هناك تعليقات: