جوزيف رايت ، السجين - توضيح لالتقاط قوي
توجد قائمة القيم "الملتقطة" أمام قائمة معلمات الإغلاق ويمكنها "التقاط" القيم من النطاق بثلاث طرق مختلفة: استخدام الارتباطات "قوية" أو "ضعيفة" أو "غير مملوكة". نستخدمها غالبًا ، وذلك بشكل أساسي لتجنب دورات مرجعية قوية ("دورات مرجعية قوية" تُعرف أيضًا باسم "الاحتفاظ بالدورات").
قد يكون من الصعب على مطور مبتدئ أن يقرر طريقة التقديم ، بحيث يمكنك قضاء الكثير من الوقت في الاختيار بين "القوي" و "الضعيف" أو بين "الضعيف" و "غير المملوك" ، ولكن بمرور الوقت ، ستدرك أن الخيار الصحيح - واحد فقط.
أولاً ، قم بإنشاء فصل بسيط:
class Singer { func playSong() { print("Shake it off!") } }
ثم نكتب دالة تنشئ
مثيلًا لفئة
Singer وتُرجع عملية إغلاق تستدعي طريقة
playSong () لفئة
Singer :
func sing() -> () -> Void { let taylor = Singer() let singing = { taylor.playSong() return } return singing }
أخيرًا ، يمكننا الاتصال بـ
sing () في أي مكان للحصول على نتيجة
لعب playSong () let singFunction = sing() singFunction()
نتيجة لذلك ، سيتم عرض السطر "التخلص منه!".
التقاط قوي
ما لم تحدد طريقة الالتقاط بشكل صريح ، يستخدم Swift التقاط "قوي". هذا يعني أن الإغلاق يلتقط القيم الخارجية المستخدمة ولن يسمح أبدًا بإطلاق سراحهم.
دعونا نلقي نظرة على وظيفة
الغناء () مرة أخرى
func sing() -> () -> Void { let taylor = Singer() let singing = { taylor.playSong() return } return singing }
يتم تعريف
ثابت تايلور داخل وظيفة ، لذلك في ظل الظروف العادية سيتم تحرير مكانها بمجرد الانتهاء من عملها. ومع ذلك ، يتم استخدام هذا الثابت داخل الإغلاق ، مما يعني أن Swift سيضمن وجوده تلقائيًا طالما كان الإغلاق نفسه موجودًا ، حتى بعد انتهاء الوظيفة.
هذا هو التقاط "قوي" في العمل. إذا سمح Swift بتحرير
تايلور ، فإن استدعاء الإغلاق سيكون غير آمن - لم تعد طريقة
taylor.playSong () صالحة.
التقاط "ضعيف" (التقاط ضعيف)
يتيح لنا Swift إنشاء "
قائمة التقاط " لتحديد كيفية التقاط القيم المستخدمة. بديل "الالتقاط القوي" هو "ضعيف" ويؤدي تطبيقه إلى النتائج التالية:
1. لا يتم الإمساك بالقيم "الضعيفة" التي تم التقاطها عن طريق الإغلاق ، وبالتالي يمكن إطلاقها وتعيينها.
2. كنتيجة للفقرة الأولى ، تكون القيم التي تم التقاطها "ضعيفًا" في Swift
اختيارية دائمًا.
نقوم بتعديل مثالنا باستخدام التقاط "ضعيف" ونرى الفرق على الفور.
func sing() -> () -> Void { let taylor = Singer() let singing = { [weak taylor] in taylor?.playSong() return } return singing }
[تايلور ضعيف] - هذه هي "
قائمة الالتقاط " الخاصة بنا ، وهي جزء خاص من بناء جملة الإغلاق الذي نقدم فيه تعليمات حول كيفية التقاط القيم. هنا نقول أنه يجب القبض على
تايلور "ضعيف" ، لذلك نحن بحاجة إلى استخدام
تايلور؟ .PlaySong () - الآن أصبح
اختياريًا ، لأنه يمكن ضبطه على
الصفر في أي وقت.
إذا قمت الآن بتنفيذ هذا الرمز ، فسترى أن الاستدعاء
singFunction () لم يعد ينتج رسالة. والسبب في ذلك هو أن
تايلور موجود فقط داخل
الغناء () ، والإغلاق المرتجع بواسطة هذه الوظيفة لا يحمل
تايلور "بقوة" داخل نفسه.
الآن حاول تغيير
taylor؟ .PlaySong () إلى
taylor! .PlaySong () . سيؤدي ذلك إلى تفريغ
تايلور قسري داخل الإغلاق ، وبالتالي إلى خطأ فادح (محتويات تفريغ تحتوي على
لا شيء )
التقاط "بدون مالك" (التقاط غير معروف)
بديل لالتقاط "ضعيف" هو "مالك".
func sing() -> () -> Void { let taylor = Singer() let singing = { [unowned taylor] in taylor.playSong() return } return singing }
سينتهي هذا الرمز بشكل غير طبيعي بطريقة اختيارية تم نشرها موضحة أعلاه - يقول
تايلور غير المعروف: "أعرف بالتأكيد أن
تايلور سيكون موجودًا طوال فترة الإغلاق ، لذلك لست بحاجة إلى الاحتفاظ به في الذاكرة." في الواقع ، سيتم إطلاق سراح
تايلور على الفور تقريبًا ، وسوف يتعطل هذا الرمز.
لذلك استخدم
unowned بعناية فائقة.
المشاكل المشتركة
هناك أربع مشاكل يواجهها المطورون عند استخدام التقاط القيمة في عمليات الإغلاق:
1. الصعوبات مع موقع قائمة الالتقاط في الحالة عندما يأخذ الإغلاق معلمات
هذه مشكلة شائعة قد تواجهها في بداية دراسة عمليات الإغلاق ، لكن لحسن الحظ ، سوف يساعدنا Swift في هذه الحالة.
عند استخدام قائمة الالتقاط ومعلمات الإغلاق معًا ، تأتي قائمة الالتقاط بين قوسين معقوفين ، ثم معلمات الإغلاق ، ثم الكلمة الأساسية ، مع تحديد بداية "الإغلاق".
writeToLog { [weak self] user, message in self?.addToLog("\(user) triggered event: \(message)") }
محاولة وضع قائمة التقاط بعد إغلاق المعلمات سيؤدي إلى خطأ في الترجمة.
2. ظهور حلقة من الروابط القوية ، مما يؤدي إلى تسرب الذاكرة
عندما يكون لدى الكيان "أ" كيان "ب" ، والعكس صحيح ، يكون لديك موقف يسمى "دورة الاحتفاظ".
كمثال ، خذ بعين الاعتبار الكود:
class House { var ownerDetails: (() -> Void)? func printDetails() { print("This is a great house.") } deinit { print("I'm being demolished!") } }
لقد حددنا فئة
المنزل ، التي تحتوي على خاصية واحدة (إغلاق) ، وطريقة واحدة ، ومزيل مهيأ للعرض يعرض رسالة عند إتلاف مثيل للفئة.
قم الآن بإنشاء فئة "
المالك" مماثلة للفئة السابقة ، فيما عدا أن خاصية الإغلاق الخاصة بها تحتوي على معلومات حول المنزل.
class Owner { var houseDetails: (() -> Void)? func printDetails() { print("I own a house.") } deinit { print("I'm dying!") } }
الآن إنشاء مثيلات هذه الفئات داخل كتلة
القيام . لا نحتاج إلى كتلة catch ، ولكن استخدام كتلة المهام سوف يدمر الحالات بعد}
print("Creating a house and an owner") do { let house = House() let owner = Owner() } print("Done")
نتيجة لذلك ، سيتم عرض الرسائل: "إنشاء منزل ومالك" ، "أنا أموت!" ، "أنا هدم!" ، ثم "تم" - كل شيء يعمل كما ينبغي.
الآن قم بإنشاء حلقة من الروابط القوية.
print("Creating a house and an owner") do { let house = House() let owner = Owner() house.ownerDetails = owner.printDetails owner.houseDetails = house.printDetails } print("Done")
الآن تظهر رسالة "إنشاء منزل ومالك" ، ثم "تم". لن يتم استدعاء مزيلات التطهير.
حدث هذا نتيجة لحقيقة أن المنزل لديه خاصية تشير إلى المالك ، وأن المالك لديه خاصية تشير إلى المنزل. لذلك ، لا يمكن إطلاق أي منها بأمان. في حالة حقيقية ، يؤدي هذا إلى حدوث تسرب للذاكرة ، مما يؤدي إلى ضعف الأداء وحتى تعطل التطبيق.
لإصلاح الموقف ، نحتاج إلى إنشاء إغلاق جديد واستخدام التقاط "ضعيف" في حالة أو حالتين ، مثل هذا:
print("Creating a house and an owner") do { let house = House() let owner = Owner() house.ownerDetails = { [weak owner] in owner?.printDetails() } owner.houseDetails = { [weak house] in house?.printDetails() } } print("Done")
ليست هناك حاجة لإعلان أن كلتا القيمتين تم التقاطهما ، فهذا يكفي للقيام بذلك في مكان واحد - سيسمح هذا لـ Swift بتدمير كلتا الفئتين عند الضرورة.
في المشروعات الحقيقية ، نادراً ما ينشأ وضع مثل هذه الحلقة الواضحة من الروابط القوية ، لكن هذا كله يتحدث عن أهمية استخدام "الضعف" في تطوير كفء.
3. الاستخدام غير المقصود للروابط القوية ، عادة عند التقاط قيم متعددة
يستخدم Swift قبضة قوية بشكل افتراضي ، مما قد يؤدي إلى سلوك غير متوقع.
النظر في التعليمات البرمجية التالية:
func sing() -> () -> Void { let taylor = Singer() let adele = Singer() let singing = { [unowned taylor, adele] in taylor.playSong() adele.playSong() return } return singing }
الآن لدينا قيمتان تم التقاطهما عن طريق الإغلاق ، ونستخدم كلاهما بالطريقة نفسها. ومع ذلك ،
يتم التقاط
تايلور فقط باعتباره غير مملوك -
يتم التقاط
أديل بقوة لأنه يجب استخدام الكلمة
غير المملوكة لكل قيمة تم التقاطها.
إذا قمت بذلك عن قصد ، فكل شيء على ما يرام ، ولكن إذا كنت تريد أن يتم التقاط كلتا القيمتين "
غير مملوك " ، فأنت تحتاج إلى ما يلي:
[unowned taylor, unowned adele]
4. نسخ الإغلاق وتبادل القيم التي تم التقاطها
الحالة الأخيرة التي يتعثر فيها المطورون هي كيفية نسخ الأخطاء لأن البيانات التي يلتقطونها تصبح متاحة لجميع نسخ الخطأ.
ضع في اعتبارك مثال على الإغلاق البسيط الذي يلتقط الرقم الصحيح للعدد المتغير
OffLinesLogged المعلن عنه خارج الإغلاق ، حتى نتمكن من زيادة قيمته وطباعته في كل مرة يتم فيها استدعاء الإغلاق:
var numberOfLinesLogged = 0 let logger1 = { numberOfLinesLogged += 1 print("Lines logged: \(numberOfLinesLogged)") } logger1()
سيعرض هذا الرسالة "الخطوط المسجلة: 1".
الآن سنقوم بإنشاء نسخة من الإغلاق التي ستشارك القيم التي تم التقاطها مع الإغلاق الأول. وبالتالي ، إذا نسمي الإغلاق الأصلي أو نسخته ، فسنرى القيمة المتزايدة للمتغير.
let logger2 = logger1 logger2() logger1() logger2()
سيؤدي ذلك إلى طباعة الرسائل "الخطوط المسجلة: 1" ... "الخطوط المسجلة: 4" لأن
logger1 و
logger2 يشيران إلى نفس رقم
numberOfLinesLogged الذي تم
التقاطه .
متى تستخدم طريقة التقاط "قوية" و "ضعيفة" و "بدون مالك"
الآن بعد أن فهمنا كيف يعمل كل شيء ، دعونا نحاول تلخيص:
1. إذا كنت متأكدًا من أن القيمة التي تم التقاطها لن تصبح أبدًا عند تنفيذ الإغلاق ، فيمكنك استخدام
"الالتقاط غير المملوك" . هذا موقف نادر الحدوث حيث يمكن أن يؤدي استخدام الالتقاط "الضعيف" إلى صعوبات إضافية ، حتى عند استخدام الواقي للسماح بقيمة تم التقاطها بشكل ضعيف داخل الإغلاق.
2. إذا كانت لديك حالة دورة من الارتباطات القوية (الكيان أ يمتلك الكيان ب ، والكيان ب يمتلك الكيان أ) ، فأنت في إحدى الحالات تحتاج إلى استخدام
"الالتقاط الضعيف" . من الضروري مراعاة أي من الكيانين سيصدر أولاً ، لذا إذا كان جهاز العرض view A يمثل جهاز التحكم في العرض B ، فقد تحتوي وحدة التحكم B على رابط "ضعيف" يعود إلى "A".
3. في حالة استبعاد احتمال وجود حلقة من الروابط القوية ، يمكنك استخدام الالتقاط
"القوي" (
"الالتقاط القوي" ). على سبيل المثال ، لا يؤدي تنفيذ الرسوم المتحركة إلى حظر النفس داخل الإغلاق الذي يحتوي على الرسوم المتحركة ، بحيث يمكنك استخدام الربط القوي.
4. إذا لم تكن متأكدًا ، فابدأ بربط "ضعيف" وقم بتغييره فقط إذا لزم الأمر.
اختياري - دليل سويفت الرسمي:
دوائر قصيرةالعد التلقائي الارتباط