سويفت: ARC وإدارة الذاكرة

نظرًا لكونه لغة حديثة عالية المستوى ، فإن Swift يعتني بشكل أساسي بإدارة الذاكرة في تطبيقاتك ، مع تخصيص الذاكرة وتحريرها. ويرجع ذلك إلى آلية تسمى العد المرجعي التلقائي ، أو ARC لفترة قصيرة. في هذا الدليل ، سوف تتعلم كيفية عمل ARC وكيفية إدارة الذاكرة بشكل صحيح في Swift. فهم هذه الآلية ، يمكنك التأثير على عمر الكائنات الموجودة في الكومة ( كومة الذاكرة المؤقتة ).

في هذا الدليل ، ستعتمد على معرفتك بـ Swift و ARC من خلال تعلم ما يلي:

  • كيف يعمل ARC
  • ما هي الدورات المرجعية وكيفية اصلاحها بشكل صحيح
  • كيفية إنشاء حلقة مثال الارتباط
  • كيفية العثور على حلقات الارتباط باستخدام الأدوات البصرية التي تقدمها Xcode
  • كيفية التعامل مع أنواع المرجع وأنواع القيمه

الابتداء


قم بتنزيل المواد المصدر. افتح المشروع في مجلد Cycles / Starter . في الجزء الأول من دليلنا ، لفهم المفاهيم الأساسية ، سنتعامل حصريًا مع ملف MainViewController.swif t.

أضف هذه الفئة في أسفل MainViewController.swift:

class User { let name: String init(name: String) { self.name = name print("User \(name) was initialized") } deinit { print("Deallocating user named: \(name)") } } 

يتم تعريف فئة المستخدم هنا ، والتي ، بمساعدة البيانات المطبوعة ، تشير إلينا حول تهيئة وإصدار مثيل الفئة.

الآن إنشاء مثيل لفئة المستخدم في الجزء العلوي من MainViewController.

ضع هذا الرمز قبل طريقة viewDidLoad () :

 let user = User(name: "John") 

قم بتشغيل التطبيق. اجعل وحدة تحكم Xcode مرئية باستخدام Command-Shift-Y لرؤية إخراج عبارات الطباعة.

لاحظ أن المستخدم John تمت تهيئته ظهر على وحدة التحكم ، ولكن لم يتم تنفيذ عبارة الطباعة داخل deinit . هذا يعني أنه لم يتم إصدار هذا الكائن ، لأنه لم يخرج عن نطاقه .

بمعنى آخر ، إلى أن يخرج عنصر تحكم العرض الذي يحتوي على هذا الكائن عن نطاقه ، فلن يتم تحرير الكائن أبدًا.

هل هو في النطاق؟


من خلال التفاف مثيل لفئة المستخدم في إحدى الطرق ، سنسمح لها بالخروج عن نطاقها ، مما يتيح لـ ARC تحريره.

دعونا إنشاء طريقة runScenario () داخل فئة MainViewController ونقل التهيئة لمثيل فئة المستخدم بداخله.

 func runScenario() { let user = User(name: "John") } 

يعرّف runScenario () نطاق مثيل المستخدم. عند الخروج من هذه المنطقة ، يجب تحرير المستخدم .

اتصل الآن بـ runScenario () مضيفًا ذلك في نهاية viewDidLoad ():

 runScenario() 

قم بتشغيل التطبيق. يبدو إخراج وحدة التحكم الآن كما يلي:

تمت تهيئة المستخدم John
إلغاء تخصيص المستخدم المسمى: جون

هذا يعني أنك قمت بإطلاق كائن غادر مجال الرؤية.

عمر الكائن



ينقسم وجود الكائن إلى خمس مراحل:

  • تخصيص الذاكرة: من المكدس أو من الكومة
  • التهيئة: يتم تنفيذ التعليمات البرمجية داخل الحرف الأول
  • استخدام
  • deinitialization: يتم تنفيذ التعليمات البرمجية داخل deinit
  • ذاكرة خالية: يتم إرجاع الذاكرة المخصصة إلى المكدس أو الكومة

لا توجد طريقة مباشرة لتتبع خطوات تخصيص وتحرير الذاكرة ، ولكن يمكنك استخدام الكود داخل الحرف الأول والتعريف.

تُحدد التعدادات المرجعية ، والمعروفة أيضًا باسم تعدادات الاستخدام ، متى لم تعد هناك حاجة إلى كائن. يوضح هذا العداد عدد من "استخدام" هذا الكائن. يصبح الكائن غير ضروري عندما يكون عداد الاستخدام صفرًا. ثم يتم إلغاء تهيئة الكائن وإصداره.



عند تهيئة كائن المستخدم ، يكون عدد مرجعه هو 1 ، لأن ثابت المستخدم يشير إلى هذا الكائن.

في نهاية runScenario () ، يخرج المستخدم عن نطاقه ويتم تقليل عدد المرجع إلى 0. ونتيجة لذلك ، يتم إلغاء تأهيل المستخدم ثم تحريره.

دورات مرجعية


في معظم الحالات ، يعمل ARC كما يجب. لا يحتاج المطور عادةً إلى القلق بشأن تسرب الذاكرة عندما تظل الكائنات غير المستخدمة غير مخصصة إلى أجل غير مسمى.

لكن ليس دائما! تسرب الذاكرة ممكن.

كيف يمكن أن يحدث هذا؟ تخيل موقفًا لم يعد يستخدم فيه كائنان ، لكن كل منهما يشير إلى الآخر. بما أن كل عدد مرجعي ليس 0 ، فلن يتم تحرير أي منها.



هذه هي دورة مرجعية قوية . هذا الموقف يربك ARC ولا يسمح له بمسح الذاكرة.

كما ترى ، فإن عدد المرجع في النهاية ليس 0 ، وعلى الرغم من عدم الحاجة إلى أي كائنات بعد الآن ، فلن يتم تحرير object1 و object2.

تحقق من الروابط لدينا


لاختبار كل هذا في العمل ، أضف هذا الكود بعد فئة المستخدم في MainViewController.swift:

 class Phone { let model: String var owner: User? init(model: String) { self.model = model print("Phone \(model) was initialized") } deinit { print("Deallocating phone named: \(model)") } } 

يضيف هذا الرمز فئة هاتف جديدة مع خاصيتين ، أحدهما للطراز والآخر للمالك ، بالإضافة إلى أساليب init و deinit. ملكية المالك اختيارية ، نظرًا لأن الهاتف قد لا يكون لديه مالك.

الآن أضف هذا السطر إلى runScenario ():

 let iPhone = Phone(model: "iPhone Xs") 

سيؤدي ذلك إلى إنشاء مثيل لفئة الهاتف.

امسك المحمول


أضف الآن هذا الكود إلى فئة المستخدم ، مباشرة بعد خاصية الاسم:

 private(set) var phones: [Phone] = [] func add(phone: Phone) { phones.append(phone) phone.owner = self } 

أضف مجموعة من الهواتف التي يملكها المستخدم. تم وضع علامة على الضابط على أنه خاص ، لذا يجب استخدام إضافة (هاتف :).

قم بتشغيل التطبيق. كما ترون ، يتم تحرير مثيلات فئات كائنات الهاتف والمستخدم حسب الحاجة.

تمت تهيئة المستخدم John
تم تهيئة هاتف iPhone XS
إلغاء تخصيص الهاتف المسمى: iPhone Xs
إلغاء تخصيص المستخدم المسمى: جون

الآن أضف هذا في نهاية runScenario ():
 user.add(phone: iPhone) 


نضيف هنا هاتف iPhone الخاص بنا إلى قائمة الهواتف التي يملكها المستخدم ، ونقوم أيضًا بتعيين خاصية مالك الهاتف على " مستخدم ".

قم بتشغيل التطبيق مرة أخرى. سترى أنه لا يتم تحرير كائنات المستخدم و iPhone. دورة الروابط القوية بينهما تمنع ARC من إطلاقها.



روابط ضعيفة


لكسر حلقة الروابط القوية ، يمكنك تعيين العلاقة بين الكائنات على أنها ضعيفة.

افتراضيًا ، تكون جميع الارتباطات قوية ويؤدي التعيين إلى زيادة في عدد المرجع. عند استخدام مراجع ضعيفة ، لا يزيد عدد المرجع.

بمعنى آخر ، لا تؤثر الروابط الضعيفة على إدارة حياة كائن ما . دائما يتم الإعلان عن الروابط الضعيفة اختياريًا . وبهذه الطريقة ، عندما يصبح عدد الارتباطات 0 ، يمكن ضبط الارتباط على الصفر.



في هذا الرسم التوضيحي ، تشير الخطوط المتقطعة إلى روابط ضعيفة. لاحظ أن عدد مرجع الكائن 1 هو 1 ، لأن المتغير 1 يشير إليه. عدد مرجع الكائن 2 هو 2 ، لأنه يتم الرجوع إليها بواسطة variable2 و object1.

يشير object2 أيضًا إلى object1 ، ولكن WEAK ، مما يعني أنه لا يؤثر على عدد مرجع object1.

عندما يتم تحرير متغير 1 ومتغير ، يكون للكائن 1 عدد مرجعي يساوي 0 ، والذي يحرره. هذا ، بدوره ، يصدر إشارة قوية إلى object2 ، مما يؤدي بالفعل إلى صدوره.

في فئة الهاتف ، قم بتغيير إعلان خاصية المالك كما يلي:

 weak var owner: User? 

بإعلان أن مرجع خاصية المالك "ضعيف" ، فإننا نقطع حلقة الروابط القوية بين فئتي المستخدم والهاتف.



قم بتشغيل التطبيق. الآن يتم إطلاق المستخدم والهاتف بشكل صحيح.

روابط غير مملوكة


هناك أيضًا معدل ارتباط آخر لا يؤدي إلى زيادة عدد المرجع: غير معروف .

ما هو الفرق بين غير ضعيف والضعيف ؟ المرجع الضعيف دائمًا اختياري ويصبح تلقائيًا لا شيء عند تحرير الكائن المشار إليه.

لهذا السبب يجب أن نعلن عن الخصائص الضعيفة كمتغير اختياري للنوع: يجب تغيير هذه الخاصية.

في المقابل ، فإن الروابط غير المعروفة ليست اختيارية. إذا حاولت الوصول إلى خاصية غير مملوكة والتي تشير إلى كائن تم تحريره ، فستظهر لك خطأ يبدو وكأنه ملفوفًا يحتوي على متغير لا شيء (فرض إلغاء التعبئة).



دعونا نحاول تطبيق غير معروف .

أضف فئة CarrierSubscription جديدة في نهاية MainViewController.swift:

 class CarrierSubscription { let name: String let countryCode: String let number: String let user: User init(name: String, countryCode: String, number: String, user: User) { self.name = name self.countryCode = countryCode self.number = number self.user = user print("CarrierSubscription \(name) is initialized") } deinit { print("Deallocating CarrierSubscription named: \(name)") } } 

يحتوي CarrierSubscription على أربع خصائص:

الاسم: اسم المزود.
كود البلد: كود البلد.
رقم: رقم الهاتف.
المستخدم: رابط إلى المستخدم.

من هو مزودك؟


الآن قم بإضافة هذا إلى فئة المستخدم بعد خاصية الاسم:

 var subscriptions: [CarrierSubscription] = [] 

نحن هنا نحتفظ بمجموعة من موفري المستخدم.

أضف الآن هذا إلى فئة الهاتف ، بعد خاصية المالك:

 var carrierSubscription: CarrierSubscription? func provision(carrierSubscription: CarrierSubscription) { self.carrierSubscription = carrierSubscription } func decommission() { carrierSubscription = nil } 

يؤدي ذلك إلى إضافة خاصية CarrierSubscription الاختيارية وطريقتين لتسجيل وإلغاء تسجيل الهاتف مع الموفر.

أضف الآن فئة CarrierSubscription داخل طريقة init ، مباشرة قبل بيان الطباعة:

 user.subscriptions.append(self) 

نضيف CarrierSubscription إلى مجموعة موفري المستخدم.

أخيرًا ، أضف هذا في نهاية الأسلوب runScenario ():

 let subscription = CarrierSubscription( name: "TelBel", countryCode: "0032", number: "31415926", user: user) iPhone.provision(carrierSubscription: subscription) 

نقوم بإنشاء اشتراك في الموفر للمستخدم وتوصيل الهاتف به.

قم بتشغيل التطبيق. في وحدة التحكم سترى:

تمت تهيئة المستخدم John
تم تهيئة هاتف iPhone Xs
تتم تهيئة CarrierSubscription TelBel

ومرة أخرى حلقة الارتباط! المستخدم ، iPhone والاشتراك لم تكن حرة في النهاية.

هل يمكنك العثور على مشكلة؟



كسر السلسلة


يجب أن يكون الرابط من مستخدم إلى اشتراك أو الارتباط من اشتراك إلى مستخدم غير مفضل لكسر الحلقة. والسؤال هو أي خيار للاختيار. دعونا ننظر إلى الهياكل.

يمتلك المستخدم اشتراكًا في موفر ، لكن العكس - لا ، لا يمتلك اشتراك موفر مستخدمًا.

علاوة على ذلك ، لا يوجد أي نقطة في وجود CarrierSubscription دون الرجوع إلى المستخدم الذي يملكها.

لذلك ، يجب أن يكون ارتباط المستخدم غير مملوك.

تغيير إعلان المستخدم في CarrierSubscription:

 unowned let user: User 

الآن المستخدم غير معروف ، والذي يكسر حلقة الروابط ويسمح لك بتحرير جميع الكائنات.



حلقة الروابط في الإغلاق


تحدث دورات الارتباط للكائنات عندما يكون للكائنات خصائص تشير إلى بعضها البعض. مثل الكائنات ، عمليات الإغلاق هي نوع مرجعي ، ويمكن أن تؤدي إلى حلقات مرجعية. الإغلاق التقاط الكائنات التي يستخدمونها.

على سبيل المثال ، إذا قمت بتعيين إغلاق لخاصية فئة ما ، وكان هذا الإغلاق يستخدم خصائص نفس الفئة ، فسنحصل على حلقة من الارتباطات. بمعنى آخر ، الكائن يحمل رابطًا للإغلاق من خلال الخاصية. يحتوي الإغلاق على إشارة إلى الكائن من خلال القيمة التي تم التقاطها للذات.



أضف هذا الرمز إلى CarrierSubscription مباشرة بعد خاصية المستخدم:

 lazy var completePhoneNumber: () -> String = { self.countryCode + " " + self.number } 

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

يعد ذلك ضروريًا لأنه يستخدم self.countryCode و self.number ، والذي لن يكون متاحًا حتى يتم تنفيذ رمز المُهيئ.

أضف runScenario () إلى النهاية:

 print(subscription.completePhoneNumber()) 

استدعاء CompletePhoneNumber () سيتم تنفيذ الإغلاق.

قم بتشغيل التطبيق وسترى أن المستخدم و iPhone قد تم إطلاقهما ، لكن CarrierSubscription ليس كذلك ، بسبب وجود حلقة من الروابط القوية بين الكائن والإغلاق.



القبض على قوائم


يوفر Swift طريقة بسيطة وأنيقة لكسر حلقة الروابط القوية في عمليات الإغلاق. تعلن قائمة الالتقاط التي تحدد فيها العلاقة بين الإغلاق والكائنات التي يلتقطها.

لشرح قائمة الالتقاط ، خذ بعين الاعتبار الكود التالي:

 var x = 5 var y = 5 let someClosure = { [x] in print("\(x), \(y)") } x = 6 y = 6 someClosure() // Prints 5, 6 print("\(x), \(y)") // Prints 6, 6 

x في قائمة الالتقاط ، وبالتالي يتم نسخ قيمة x إلى تعريف الإغلاق. يتم التقاطها من قبل القيمة.

y ليست في قائمة الالتقاط ، يتم التقاطها بالرجوع إليها. هذا يعني أن قيمة y ستكون كما كانت في وقت تم استدعاء الدائرة.

تساعد قوائم التأمين في تحديد التفاعلات الضعيفة أو غير المعروفة فيما يتعلق بالكائنات التي يتم التقاطها داخل الحلقة. في حالتنا ، يكون الخيار المناسب غير مملوك ، نظرًا لعدم وجود إغلاق إذا تم إصدار مثيل CarrierSubscription.

اغتنم نفسك


استبدال تعريف CompletePhoneNumber بـ CarrierSubscription ::

 lazy var completePhoneNumber: () -> String = { [unowned self] in return self.countryCode + " " + self.number } 

نضيف [الذات غير المملوكة] إلى قائمة التقاط الإغلاق. هذا يعني أننا استحوذنا على النفس على أنها رابط غير مملوك بدلاً من الارتباط القوي.

قم بتشغيل التطبيق وسترى إصدار CarrierSubscription الآن.

في الواقع ، بناء الجملة أعلاه هو شكل قصير من نموذج أطول وأكثر اكتمالا يظهر فيه متغير جديد:

 var closure = { [unowned newID = self] in // Use unowned newID here... } 

newID هنا هو نسخة غير مملوكة من النفس. ما بعد الإغلاق ، تبقى النفس نفسها. في النموذج القصير المعطى مسبقًا ، نقوم بإنشاء متغير ذاتي جديد يحجب الذات الموجودة داخل الإغلاق.

استخدم Unowned بعناية


في التعليمات البرمجية الخاصة بك ، تم تعيين العلاقة بين self و completePhoneNumber على أنها غير مملوكة.

إذا كنت متأكدًا من أن الكائن المستخدم في الإغلاق لن يتم تحريره ، فيمكنك استخدام غير مملوك. إذا فعل ذلك ، فأنت في ورطة!

أضف هذا الرمز في نهاية MainViewController.swift:

 class WWDCGreeting { let who: String init(who: String) { self.who = who } lazy var greetingMaker: () -> String = { [unowned self] in return "Hello \(self.who)." } } 

الآن هنا نهاية runScenario ():

 let greetingMaker: () -> String do { let mermaid = WWDCGreeting(who: "caffeinated mermaid") greetingMaker = mermaid.greetingMaker } print(greetingMaker()) // ! 

قم بتشغيل التطبيق وسترى تعطلًا وشيء من هذا القبيل في وحدة التحكم:

تمت تهيئة المستخدم John
تم تهيئة هاتف iPhone XS
تتم تهيئة CarrierSubscription TelBel
0032 31415926
خطأ فادح: جرت محاولة قراءة مرجع غير مملوك ولكن تم إلغاء تخصيص الكائن 0x600000f0de30 بالفعل 2019-02-24 12: 29: 40.744248-0600 دورات [33489: 5926466] خطأ فادح: جرت محاولة قراءة مرجع غير مملوك ولكن الكائن 0x600000f0de30

حدث استثناء لأن الإغلاق ينتظر وجود self.who ، ولكن تم إصداره بمجرد خروج mermaid عن نطاقه في نهاية الكتلة do.

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

نزع فتيل الفخ


استبدل greetingMaker في فئة WWDCGreeting بهذا:

 lazy var greetingMaker: () -> String = { [weak self] in return "Hello \(self?.who)." } 

لقد فعلنا شيئين: أولاً ، استبدلنا بالضعفاء. ثانياً ، منذ أن أصبحت الذات ضعيفة ، يمكننا الوصول إلى من هو من خلال الذات؟ تجاهل تحذير Xcode ، سنقوم بإصلاحه قريبًا.

لم يعد التطبيق يتعطل ، ولكن إذا قمت بتشغيله ، فستحصل على نتيجة مضحكة: "Hello nil".

ربما تكون النتيجة مقبولة تمامًا ، لكن غالبًا ما نحتاج إلى فعل شيء ما إذا تم تحرير الكائن. ويمكن القيام بذلك باستخدام بيان الحرس.

استبدل نص الإغلاق بهذا:

 lazy var greetingMaker: () -> String = { [weak self] in guard let self = self else { return "No greeting available." } return "Hello \(self.who)." } 

بيان الحراسة يعين النفس مأخوذة من ضعف الذات. إذا كانت النفس لا شيء ، فإن الإغلاق يعود "لا توجد تحية متاحة". خلاف ذلك ، تصبح النفس مرجعًا قويًا ، وبالتالي فإن الكائن مضمون للعيش حتى نهاية الإغلاق.

تبحث عن حلقات الارتباط في Xcode 10


الآن بعد أن أدركت كيفية عمل ARC ، وما هي حلقات الارتباط وكيفية كسرها ، فقد حان الوقت لرؤية مثال على تطبيق حقيقي.

افتح مشروع Starter الموجود في مجلد جهات الاتصال.

قم بتشغيل التطبيق.



هذا هو أبسط مدير الاتصال. جرِّب النقر فوق جهة اتصال ، وإضافة اثنين من جهات الاتصال الجديدة.

احالة الملف:

ContactsTableViewController: يظهر جميع جهات الاتصال.
DetailViewController: يعرض المعلومات التفصيلية لجهة الاتصال المحددة.
NewContactViewController: يسمح لك بإضافة جهة اتصال جديدة.
ContactTableViewCell: خلية الجدول تظهر تفاصيل الاتصال.
الاتصال: نموذج الاتصال.
رقم: نموذج رقم الهاتف.

ومع ذلك ، مع هذا المشروع ، كل شيء سيء: هناك حلقة من الروابط. في البداية ، لن يلاحظ المستخدمون المشكلات بسبب صغر حجم الذاكرة المتسربة ، ولهذا السبب يصعب العثور على التسرب.

لحسن الحظ ، يحتوي Xcode 10 على أدوات مضمّنة للعثور على أصغر تسرب للذاكرة.

قم بتشغيل التطبيق مرة أخرى. احذف 3-4 جهات اتصال باستخدام التمرير السريع إلى اليسار وزر الحذف. يبدو أنها تختفي تماما ، أليس كذلك؟



أين تتدفق؟


عند تشغيل التطبيق ، انقر فوق الزر Debug Memory Graph:



راقب مشكلات وقت التشغيل في متصفح Debug. تم وضع علامة عليها بمربعات أرجوانية تحمل علامة تعجب بيضاء بداخلها:



حدد أحد كائنات جهة الاتصال التي بها مشكلات في لوحة التجول. الدورة مرئية بوضوح: كائنات الاتصال والرقم ، في إشارة إلى بعضها البعض ، معلقة.



يبدو أنك يجب أن ننظر إلى الرمز. ضع في اعتبارك أن جهة الاتصال يمكن أن توجد بدون رقم ، ولكن ليس العكس.

كيف يمكنك حل هذه الحلقة؟ رابط من جهة الاتصال إلى رقم أو من رقم إلى جهة اتصال؟ ضعيف أم لا؟ جربها بنفسك أولاً!

إذا كنت بحاجة إلى مساعدة ...
هناك حلان محتملان: إما جعل الرابط من جهة الاتصال إلى الرقم ضعيفًا ، أو من رقم إلى جهة اتصال غير معروف.

توصي وثائق Apple بأن يحتوي الكائن الأصل على إشارة قوية إلى "تابع" - وليس العكس. هذا يعني أننا نعطي جهة الاتصال إشارة قوية إلى الرقم ، والرقم - رابط غير مملوك لجهة الاتصال:

 class Number { unowned var contact: Contact // Other code... } class Contact { var number: Number? // Other code... } 


المكافأة: حلقات مع أنواع المراجع وأنواع القيمة.


يحتوي Swift على أنواع مرجعية (فئات وإغلاقات) وأنواع قيمة (بنيات ، أعداد). يتم نسخ نوع القيمة عندما يتم تمريره ، وأنواع المرجع تشترك في نفس القيمة باستخدام الرابط.

هذا يعني أنه في حالة أنواع القيم ، لا يمكن أن تكون هناك دورات. لكي تحدث حلقة ، نحتاج إلى نوعين على الأقل من المراجع.

دعنا نعود إلى مشروع Cycles وأضف هذا الكود في نهاية MainViewController.swift:

 struct Node { // Error var payload = 0 var next: Node? } 

لن تعمل! الهيكل هو نوع قيمة ولا يمكن أن يكون له تكرار على مثيله نفسه. خلاف ذلك ، فإن مثل هذا الهيكل سيكون لها حجم لانهائي.

تغيير الهيكل إلى فئة.

 class Node { var payload = 0 var next: Node? } 

المرجع إلى نفسه مقبول تمامًا للفئات (نوع المرجع) ، لذلك لا يواجه المترجم مشاكل.

الآن أضف هذا في نهاية MainViewController.swift:

 class Person { var name: String var friends: [Person] = [] init(name: String) { self.name = name print("New person instance: \(name)") } deinit { print("Person instance \(name) is being deallocated") } } 

وهذا في نهاية runScenario ():

 do { let ernie = Person(name: "Ernie") let bert = Person(name: "Bert") ernie.friends.append(bert) // Not deallocated bert.friends.append(ernie) // Not deallocated } 

قم بتشغيل التطبيق. يرجى ملاحظة: لا يتم الافراج عن إرني ولا بيرت.

رابط ومعنى


هذا مثال على مزيج من نوع المرجع ونوع القيمة الذي أدى إلى حلقة من الارتباطات.

يظل إرني وبيرت بدون إفراج عنهما ، ويحتفظان ببعضهما البعض في صفائف أصدقائهما ، على الرغم من أن المصفوفات نفسها أنواع قيمة.

حاول جعل أرشيف الأصدقاء غير مملوك ، وسيظهر Xcode خطأ: ينطبق unowned فقط على الفصول الدراسية.

لإصلاح هذه الحلقة ، يتعين علينا إنشاء كائن مجمّع واستخدامه لإضافة مثيلات إلى الصفيف.

أضف التعريف التالي قبل فئة الشخص:

 class Unowned<T: AnyObject> { unowned var value: T init (_ value: T) { self.value = value } } 

ثم قم بتغيير تعريف الأصدقاء في فئة الشخص:

 var friends: [Unowned<Person>] = [] 

أخيرًا ، استبدل محتويات كتلة المهام في runScenario ():

 do { let ernie = Person(name: "Ernie") let bert = Person(name: "Bert") ernie.friends.append(Unowned(bert)) bert.friends.append(Unowned(ernie)) } 

قم بتشغيل التطبيق ، والآن يتم إصدار ernie و bert بشكل صحيح!

مجموعة الأصدقاء لم تعد مجموعة من كائنات الشخص. هذه هي الآن مجموعة من الكائنات غير المعروفة التي تعمل كمغلفات لمثيلات الأشخاص.

للحصول على كائنات شخص من Unowned ، استخدم خاصية القيمة:

 let firstFriend = bert.friends.first?.value // get ernie 

استنتاج


لديك الآن فهم جيد لإدارة الذاكرة في Swift وأنت تعرف كيف يعمل ARC. آمل أن يكون المنشور مفيدًا لك.

التفاح: العد المرجعي التلقائي

Source: https://habr.com/ru/post/ar451130/


All Articles