ios - استخدام ضعف الذات في وظيفة dispatch_async




objective-c-blocks weak-references (2)

قرأت الكثير من المشاركات حول استخدام __weak self داخل dispatch_async ، والآن أشعر بالارتباك قليلاً.

لو كان لدي :

self.myQueue = dispatch_queue_create("com.biview.core_data", NULL);

dispatch_async(self.myQueue, ^(void){
    if (!self.var1) {
        self.var1 = ...;
    }
    dispatch_async(dispatch_get_main_queue(), ^(void) {
        if ([self.var2 superview]) {
            [self.var2 removeFromSuperview];
        }

        [self.Label setText:text];
    });
});

أحتاج إلى استخدام __weak self . لأنني قرأت أنه في بعض الحالات ، لا تحتاج dispatch_async إلى __weak self .

انظر التعليق الاخير هنا


تحديث سريع:

مثال على هذا ما يسمى الرقص الضعيف القوي بسرعة:

سويفت 4.2:

func doSomeThingAsynchronously() {
    DispatchQueue.global().async {
        // Do task in default queue
        DispatchQueue.main.async { [weak self] in
            // Do task in main queue
            guard let self = self else { return }
            self.updateView()
        }
    }
}

سويفت 3 و 4:

func doSomeThingAsynchronously() {
    DispatchQueue.global().async {
        // Do task in default queue
        DispatchQueue.main.async { [weak self] in
            // Do task in main queue
            guard let strongSelf = self else { return }
            strongSelf.updateView()
        }
    }
}

سويفت 2:

func doSomeThingAsynchronously() {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> () in
        // Do task in default queue
        dispatch_async(dispatch_get_main_queue(), { [weak self] () -> () in
            guard let strongSelf = self else { return }
            // Do task in main queue
            strongSelf.updateView()
        })
    }
}

مشروع مفتوح المصدر Alamofire يستخدم Alamofire هذا النهج.

قم بتمديد عمر الكائن باستخدام [ضعيف النفس] واترك واقي القوّة القوية = تعبير آخر.

لمزيد من المعلومات ، راجع swift-style-guide


بفرض ، self هو مؤشر كائن إلى UIViewController .

أشياء للإعتبار:

  • UIViewController هو كائن "UIKit". يجب عدم إرسال كائنات UIKit على سلاسل مؤشرات الترابط غير الرئيسية ، أي - يجب تنفيذ تلك الطرق في سلسلة الرسائل الرئيسية فقط!

  • سيتم في النهاية تنفيذ كتلة تم وضعها في قائمة انتظار - سواء تم ذلك بشكل متزامن أو غير متزامن - بغض النظر عن! حسنًا ، ما لم يتم إنهاء البرنامج قبل أن يحدث ذلك.

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

  • لن يتم الاحتفاظ بالمؤشرات الضعيفة القابلة للإمساك ولن يتم إصدارها.

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

اذا لماذا؟ وماذا يحدث في الواقع؟

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

وهذا يعني ، سيتم تمديد فترة حياة الذات حتى بعد انتهاء الكتلة. لاحظ أنه تم إرسال الكتلة الثانية على الخيط الرئيسي ، وأنه يضمن أن النفس لا تزال حية عند تنفيذ هذه الكتلة.

قد تكون هذه "العمر الطويل" أعلاه ميزة مطلوبة في البرنامج.

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

قد ترغب في عدم عمل الكتلة "لا شيء" إذا تم إلغاء تخصيص UIViewController قبل تنفيذ الكتلة:

MyController* __weak weakSelf = self;
dispatch_async(queue, ^{
    MyController* strongSelf = weakSelf;
    if (strongSelf) {
        ...
    }
    else {
       // self has been deallocated in the meantime.
    }
});

راجع أيضًا: الانتقال إلى ملاحظات إصدار ARC

تذكر: لا يجوز إرسال كائنات UIKit في المواضيع غير الرئيسية!

قد يحدث خطأ خفي آخر يتعلق بحقيقة أن كائنات UIKit يجب أن تنفذ الطرق على الخيط الرئيسي فقط.

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

يمكنك تجنب هذا الخطأ ، عن طريق إرسال كتلة تلتقط مؤشرًا قويًا لكائن UIKit ، وترسلها بطريقة وهمية:

UIViewController* strongUIKitPointer = ... 
dispatch_async(non_main_queue, ^{
    ... // do something 
    dispatch(dispatch_get_main_queue(), ^{
        [strongUIKitPointer self];  // note: self is a method, too - doing nothing
    });
});

في السيناريو الخاص بك ، يمكن أن يكون المرجع القوي الأخير فقط في الكتلة التي تنفذ على سلسلة الرسائل الرئيسية. لذلك ، أنت في مأمن من هذا الخطأ الخفي. ؛)

تصحيح:

في الإعداد الخاص بك ، لم يكن لديك دورة الاحتفاظ. تحدث دورة الاحتفاظ إذا كان كائن يمكن الاحتفاظ به يشير بقوة إلى كائن آخر يمكن الاحتفاظ به B ، ويشير الكائن B بشدة إلى A. لاحظ أن "الحظر" هو أيضًا كائن يمكن الاحتفاظ به.

مثال مفتعل مع مرجع دوري:

typedef void(^my_completion_block_t)(NSArray* result);

@interface UsersViewController : UIViewController
@property (nonatomic, copy) my_completion_block_t completion;
@property (nonatomic) NSArray* users;
@end

هنا ، لدينا إتمام خاصية يكون نوع قيمتها عبارة عن كتلة. بمعنى ، نحصل على ivar باسم _completion الذي نوعه كتلة.

يجوز للعميل تعيين معالج إتمام والذي يجب استدعاؤه عند انتهاء عملية معينة. لنفترض أن العملية تجلب قائمة المستخدمين من خادم بعيد. تتمثل الخطة في تعيين مستخدمي العقار بمجرد انتهاء العملية:

النهج غير المبالي سوف يقدم عن طريق الخطأ مرجع دوري:

في مكان ما في "UsersViewController.m"

self.completion = ^(NSArray* users){
    self.users = users;
}

[self fetchUsers];  // start asynchronous task

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

لتجنب هذا المرجع الدوري ، لدينا عدد قليل من البدائل:

  1. باستخدام مؤشر __weak مؤهل للذات

    UsersViewController* __weak weakSelf = self;
    self.completion = ^(NSArray* users) {
        UsersViewController* strongSelf = weakSelf;
        if (strongSelf) {
            strongSelf.users = users;
        }
        else {
            // the view controller does not exist anymore
        }
    }   
    [usersViewController fetchUsers];
  2. باستخدام مؤشر مؤهل __block في نهاية المطاف nil في الكتلة عندما ينتهي:

    UsersViewController* __block blockSelf = self;
    self.completion = ^(NSArray* users) {
        blockSelf.users = users;
        blockSelf = nil;
    }   
    [usersViewController fetchUsers];

راجع أيضًا: الانتقال إلى ملاحظات إصدار ARC





retain-cycle