ส่งการอ้างอิงตนเองที่อ่อนแอไปยังบล็อกใน ARC เสมอหรือไม่


252

ฉันสับสนเล็กน้อยเกี่ยวกับการใช้งานบล็อกใน Objective-C ปัจจุบันฉันใช้ ARC และมีบล็อกจำนวนมากในแอปของฉันในปัจจุบันอ้างอิงถึงเสมอselfแทนที่จะเป็นข้อมูลอ้างอิงที่อ่อนแอ นั่นอาจเป็นสาเหตุของบล็อกเหล่านี้ในการรักษาselfและป้องกันไม่ให้ถูกจัดสรรคืน? คำถามคือฉันควรใช้การweakอ้างอิงselfในบล็อกหรือไม่

-(void)handleNewerData:(NSArray *)arr
{
    ProcessOperation *operation =
    [[ProcessOperation alloc] initWithDataToProcess:arr
                                         completion:^(NSMutableArray *rows) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateFeed:arr rows:rows];
        });
    }];
    [dataProcessQueue addOperation:operation];
}

ProcessOperation.h

@interface ProcessOperation : NSOperation
{
    NSMutableArray *dataArr;
    NSMutableArray *rowHeightsArr;
    void (^callback)(NSMutableArray *rows);
}

ProcessOperation.m

-(id)initWithDataToProcess:(NSArray *)data completion:(void (^)(NSMutableArray *rows))cb{

    if(self =[super init]){
        dataArr = [NSMutableArray arrayWithArray:data];
        rowHeightsArr = [NSMutableArray new];
        callback = cb;
    }
    return self;
}

- (void)main {
    @autoreleasepool {
        ...
        callback(rowHeightsArr);
    }
}

หากคุณต้องการคำอธิบายเชิงลึกในหัวข้อนี้อ่านdhoerl.wordpress.com/2013/04/23/…
David H

คำตอบ:


721

ช่วยไม่ให้มุ่งเน้นstrongหรือweakเป็นส่วนหนึ่งของการอภิปราย แทนที่จะมุ่งเน้นที่ส่วนวงจร

รอบการเก็บรักษาคือการวนซ้ำที่เกิดขึ้นเมื่อวัตถุ A เก็บรักษาวัตถุ B และวัตถุ B เก็บรักษาวัตถุ A. ในสถานการณ์นั้นถ้าวัตถุใดถูกปล่อยออกมา:

  • วัตถุ A จะไม่ถูกจัดสรรคืนเนื่องจาก Object B เก็บการอ้างอิงไว้
  • แต่วัตถุ B จะไม่ถูกยกเลิกการจัดสรรตราบใดที่วัตถุ A มีการอ้างอิงถึงมัน
  • แต่จะไม่ถูกยกเลิกการจัดสรรวัตถุ A เนื่องจากวัตถุ B เก็บการอ้างอิงไว้
  • ad infinitum

ดังนั้นวัตถุทั้งสองนั้นจะวางในหน่วยความจำตลอดอายุการใช้งานของโปรแกรมแม้ว่าพวกเขาควรจะถูกจัดสรรคืนหากทุกอย่างทำงานอย่างถูกต้อง

ดังนั้นสิ่งที่เรากังวลคือรักษารอบและไม่มีอะไรเกี่ยวกับบล็อกในและของตัวเองที่สร้างรอบเหล่านี้ นี่ไม่ใช่ปัญหาตัวอย่างเช่น:

[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
   [self doSomethingWithObject:obj];
}];

บล็อกยังคงรักษาselfแต่selfไม่ได้เก็บบล็อก หากมีสิ่งใดสิ่งหนึ่งถูกปล่อยออกมาจะไม่มีการสร้างวัฏจักรและทุกอย่างจะได้รับการจัดสรรคืนตามที่ควรจะเป็น

ปัญหาที่คุณต้องเผชิญคือ:

//In the interface:
@property (strong) void(^myBlock)(id obj, NSUInteger idx, BOOL *stop);

//In the implementation:
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [self doSomethingWithObj:obj];     
}];

ตอนนี้วัตถุของคุณ ( self) มีการstrongอ้างอิงที่ชัดเจนถึงบล็อก และบล็อกนั้นมีการอ้างอิงที่ชัดเจนโดยนัยselfอ้างอิงที่แข็งแกร่งในการนั่นคือวัฏจักรและตอนนี้วัตถุจะไม่ถูกจัดสรรคืนอย่างถูกต้อง

เนื่องจากในสถานการณ์เช่นนี้self ตามคำนิยามมีการstrongอ้างอิงไปยังบล็อกอยู่แล้วโดยทั่วไปการแก้ไขที่ง่ายที่สุดโดยการอ้างอิงที่อ่อนแออย่างชัดเจนเพื่อselfให้บล็อกใช้:

__weak MyObject *weakSelf = self;
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [weakSelf doSomethingWithObj:obj];     
}];

แต่นี่ไม่ควรเป็นรูปแบบเริ่มต้นที่คุณติดตามเมื่อจัดการกับบล็อกที่เรียกself! นี่ควรใช้เพื่อแยกสิ่งที่จะเป็นวัฏจักรการเก็บรักษาระหว่างตัวเองและบล็อก หากคุณต้องนำรูปแบบนี้ไปใช้ทุกที่คุณจะเสี่ยงต่อการผ่านบล็อกไปยังสิ่งที่ถูกดำเนินการหลังจากselfถูกจัดสรรคืน

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not retained!
  [weakSelf doSomething];
}];

2
ฉันไม่แน่ใจว่าการเก็บรักษาขขเก็บไว้จะทำรอบไม่สิ้นสุด จากมุมมองของการนับการอ้างอิงการนับของ A และ B คือ 1 สิ่งที่ทำให้เกิดวงจรการเก็บรักษาสำหรับสถานการณ์นี้คือเมื่อไม่มีกลุ่มอื่นที่มีการอ้างอิงที่แข็งแกร่งของ A และ B ภายนอก - หมายความว่าเราไม่สามารถเข้าถึงวัตถุทั้งสองนี้ได้ ไม่สามารถควบคุม A เพื่อปล่อย B และในทางกลับกัน) ดังนั้น A และ B จะอ้างอิงซึ่งกันและกันเพื่อให้ทั้งคู่มีชีวิตอยู่
Danyun Liu

@ Danyun ในขณะที่มันเป็นความจริงที่รักษารอบระหว่าง A และ B ไม่สามารถกู้คืนได้จนกว่าการอ้างอิงอื่น ๆ ทั้งหมดไปยังวัตถุเหล่านี้ได้รับการปล่อยตัวที่ไม่ได้ทำให้วงจรน้อยลง ในทางกลับกันเนื่องจากวัฏจักรหนึ่งที่สามารถกู้คืนได้ไม่ได้หมายความว่าคุณจะสามารถกู้คืนมันได้ในโค้ด รอบการรักษาเป็นกลิ่นของการออกแบบที่ไม่ดี
jemmons

@ jemmons ใช่เราควรหลีกเลี่ยงการออกแบบวงจรการเก็บรักษามากที่สุดเท่าที่จะทำได้
Danyun Liu

1
@Master มันเป็นไปไม่ได้ที่ฉันจะพูด ขึ้นอยู่กับการนำ-setCompleteionBlockWithSuccess:failure:วิธีการของคุณไปใช้อย่างสมบูรณ์ แต่ถ้าpaginatorเป็นเจ้าของโดยViewControllerและบล็อกเหล่านี้จะไม่ถูกเรียกหลังจากนั้นViewControllerจะถูกปล่อยออกมาโดยใช้การ__weakอ้างอิงจะเป็นการเคลื่อนที่ที่ปลอดภัย (เพราะselfเป็นเจ้าของสิ่งที่เป็นเจ้าของบล็อก แม้ว่าพวกเขาจะไม่เก็บมันไว้) แต่นั่นเป็นจำนวนมากของ "ถ้า" s มันขึ้นอยู่กับสิ่งที่ควรทำ
jemmons

1
@ ใจไม่และนี่คือหัวใจของปัญหาการจัดการหน่วยความจำที่มีบล็อก / ปิด วัตถุได้รับการจัดสรรคืนเมื่อไม่มีอะไรเป็นเจ้าของ MyObjectและSomeOtherObjectทั้งสองเป็นเจ้าของบล็อก แต่เนื่องจากบล็อกกลับอ้างอิงถึงMyObjectเป็นweakบล็อกไม่ได้MyObjectเอง ดังนั้นในขณะที่บล็อกรับประกันได้ว่าจะมีอยู่ตราบใดที่ทั้งสอง MyObject หรือ SomeOtherObjectอยู่ไม่มีการรับประกันว่าไม่มีMyObjectจะมีอยู่ตราบใดที่บล็อกไม่ MyObjectสามารถจัดสรรคืนได้อย่างสมบูรณ์และตราบใดที่SomeOtherObjectยังมีอยู่บล็อกจะยังคงอยู่
jemmons

26

คุณไม่จำเป็นต้องใช้การอ้างอิงที่อ่อนแอ หากบล็อกของคุณไม่ถูกเก็บไว้ แต่ถูกดำเนินการแล้วละทิ้งคุณสามารถจับภาพตัวเองได้อย่างรุนแรงเนื่องจากจะไม่สร้างรอบการเก็บข้อมูล ในบางกรณีคุณต้องการให้บล็อกเก็บตัวเองไว้จนกว่าจะเสร็จสิ้นบล็อกดังนั้นจึงไม่สามารถยกเลิกการจัดสรรก่อนเวลาอันควร อย่างไรก็ตามหากคุณจับภาพบล็อกอย่างแรงและภายในการจับภาพตัวเองมันจะสร้างวงจรการเก็บรักษา


ฉันเพียงแค่เรียกบล็อกเป็น callback และฉันไม่ต้องการให้ dealloced เลย แต่ดูเหมือนว่าฉันกำลังสร้างวงจรการเก็บรักษาไว้เนื่องจาก View Controller ที่เป็นปัญหาไม่ได้รับการจัดสรรคืน ...
the_critic

1
ตัวอย่างเช่นหากคุณมีรายการปุ่มบาร์ซึ่งถูกควบคุมโดยตัวควบคุมมุมมองและคุณจับภาพตัวเองอย่างแรงในบล็อกนั้นจะมีวงจรการเก็บรักษา
Leo Natan

1
@MartinE คุณควรอัปเดตคำถามของคุณด้วยตัวอย่างโค้ด ลีโอค่อนข้างถูกต้อง (+1) ซึ่งไม่จำเป็นต้องทำให้เกิดรอบการอ้างอิงที่แข็งแกร่ง แต่มันอาจขึ้นอยู่กับว่าคุณใช้บล็อกเหล่านี้อย่างไร เราจะช่วยคุณได้ง่ายขึ้นถ้าคุณให้ข้อมูลโค้ด
Rob

@LeoNatan ฉันเข้าใจความคิดของการรักษารอบ แต่ฉันไม่แน่ใจว่าสิ่งที่เกิดขึ้นในบล็อกเพื่อที่ทำให้ฉันสับสนเล็กน้อย
the_critic

ในโค้ดด้านบนอินสแตนซ์ของคุณจะถูกปล่อยออกมาเมื่อการดำเนินการเสร็จสิ้นและการเปิดตัวบล็อก คุณควรอ่านเกี่ยวกับวิธีการทำงานของบล็อกและสิ่งที่และเมื่อจับในขอบเขตของพวกเขา
Leo Natan

26

ฉันเห็นด้วยกับ @jemmons ทั้งหมด:

แต่นี่ไม่ควรเป็นรูปแบบเริ่มต้นที่คุณติดตามเมื่อจัดการกับบล็อกที่เรียกตนเอง! นี่ควรใช้เพื่อแยกสิ่งที่จะเป็นวัฏจักรการเก็บรักษาระหว่างตัวเองและบล็อก หากคุณต้องยอมรับรูปแบบนี้ทุกที่คุณจะเสี่ยงต่อการส่งต่อบล็อกไปยังบางสิ่งที่ถูกประหารชีวิตหลังจากถูกยกเลิกการจัดสรรด้วยตนเอง

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not  retained!
  [weakSelf doSomething];
}];

เพื่อเอาชนะปัญหานี้เราสามารถนิยามการอ้างอิงที่แข็งแกร่งเหนือweakSelfบล็อกด้านใน:

__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  MyObject *strongSelf = weakSelf;
  [strongSelf doSomething];
}];

3
StrongSelf จะไม่เพิ่มจำนวนการอ้างอิงสำหรับตัวอ่อนแอหรือไม่ ดังนั้นการสร้างวงจรการเก็บรักษา?
mskw

12
รักษารอบเฉพาะเมื่อพวกเขาอยู่ในสถานะคงที่ของวัตถุ ในขณะที่โค้ดกำลังดำเนินการและสถานะของมันอยู่ในฟลักซ์การเก็บรักษาที่หลากหลายและอาจซ้ำซ้อนนั้นใช้ได้ อย่างไรก็ตามเกี่ยวกับรูปแบบนี้การบันทึกการอ้างอิงที่แข็งแกร่งนั้นไม่ได้ทำอะไรเลยสำหรับกรณีที่ตัวเองได้รับการจัดสรรคืนก่อนที่บล็อกจะทำงานซึ่งยังคงเกิดขึ้นได้ มันทำให้แน่ใจว่าตนเองไม่ได้รับการจัดสรรคืนในขณะดำเนินการบล็อก เรื่องนี้จะเกิดขึ้นถ้าการบล็อกนั้นดำเนินการแบบอะซิงก์โดยจะให้หน้าต่างเพื่อให้มันเกิดขึ้น
Pierre Houston

@smallduck คำอธิบายของคุณยอดเยี่ยม ตอนนี้ฉันเข้าใจสิ่งนี้ดีขึ้นแล้ว ขอบคุณหนังสือที่ไม่ครอบคลุมถึงเรื่องนี้
Huibin Zhang

5
นี่ไม่ใช่ตัวอย่างที่ดีของ strongSelf เนื่องจากการเพิ่มที่ชัดเจนของ strongSelf นั้นเป็นสิ่งที่รันไทม์จะทำต่อไป: บนบรรทัด doSomething การอ้างอิงที่รัดกุมจะถูกนำมาใช้เป็นระยะเวลาของการเรียกเมธอด ถ้า weakSelf ถูกทำให้ใช้ไม่ได้แล้วการอ้างอิงที่คาดเดายากจะเป็นศูนย์และการเรียกเมธอดคือไม่มีการเรียกใช้ ที่ StrongSelf ช่วยคือถ้าคุณมีชุดปฏิบัติการหรือเข้าถึงเขตข้อมูลสมาชิก ( ->) ซึ่งคุณต้องการรับประกันว่าคุณได้รับการอ้างอิงที่ถูกต้องจริงและถือมันอย่างต่อเนื่องตลอดทั้งชุดปฏิบัติการเช่นif ( strongSelf ) { /* several operations */ }
Ethan

19

เมื่อเลโอชี้ให้เห็นรหัสที่คุณเพิ่มลงในคำถามของคุณจะไม่แนะนำรอบการอ้างอิงที่แข็งแกร่ง (หรือที่เรียกว่าวงรอบ) ปัญหาที่เกี่ยวข้องกับการดำเนินการอย่างหนึ่งที่อาจทำให้เกิดรอบการอ้างอิงที่รัดกุมคือหากการดำเนินการไม่ได้รับการเผยแพร่ ในขณะที่ข้อมูลโค้ดของคุณแนะนำว่าคุณยังไม่ได้กำหนดการดำเนินการของคุณให้พร้อมกัน แต่ถ้าคุณมีจะไม่ปล่อยถ้าคุณไม่เคยโพสต์isFinishedหรือถ้าคุณมีการอ้างอิงแบบวงกลมหรืออะไรทำนองนั้น และหากการดำเนินการไม่ได้รับการเปิดใช้งานตัวควบคุมมุมมองจะไม่ถูกปล่อยออกมาเช่นกัน ฉันขอแนะนำให้เพิ่มเบรกพอยต์หรือNSLogในdeallocวิธีการทำงานของคุณและยืนยันว่าได้รับการโทร

คุณพูดว่า:

ฉันเข้าใจความคิดเกี่ยวกับวัฏจักรการคงไว้ แต่ฉันไม่แน่ใจว่าเกิดอะไรขึ้นในบล็อกดังนั้นฉันจึงสับสนเล็กน้อย

ปัญหาการวนรอบ (รอบการอ้างอิงที่รัดกุม) ที่เกิดขึ้นกับบล็อกนั้นเหมือนกับปัญหาการวนรอบที่คุณคุ้นเคย บล็อกจะรักษาการอ้างอิงที่แข็งแกร่งไปยังวัตถุใด ๆ ที่ปรากฏภายในบล็อกและจะไม่ปล่อยการอ้างอิงที่รัดกุมเหล่านั้นจนกว่าจะปล่อยตัวบล็อกนั้นเอง ดังนั้นหากอ้างอิงบล็อกselfหรือเพียงแค่อ้างอิงตัวแปรอินสแตนซ์ของselfที่จะรักษาการอ้างอิงที่แข็งแกร่งเพื่อตนเองที่จะไม่ได้รับการแก้ไขจนกว่าจะเปิดตัวบล็อก (หรือในกรณีนี้จนกว่าNSOperationจะปล่อยคลาสย่อย

สำหรับข้อมูลเพิ่มเติมดูส่วนหลีกเลี่ยงการอ้างอิงที่รัดกุมเมื่อจับตัวเองของส่วนเขียนโปรแกรมด้วย Objective-C: การทำงานกับบล็อกเอกสาร

หากตัวควบคุมมุมมองของคุณยังไม่ได้รับการปล่อยตัวคุณเพียงแค่ต้องระบุว่าการอ้างอิงที่คาดเดาไม่ได้อยู่ที่NSOperationใด NSTimerเช่นกันคือการใช้ซ้ำ หรือdelegateวัตถุที่กำหนดเองหรือวัตถุอื่น ๆ ที่มีการเก็บรักษาstrongข้อมูลอ้างอิงอย่างผิดพลาด คุณสามารถใช้เครื่องมือเพื่อติดตามวัตถุที่ได้รับการอ้างอิงที่แข็งแกร่งเช่น:

นับการอ้างอิงบันทึกเป็น Xcode 6

หรือใน Xcode 5:

นับการอ้างอิงบันทึกใน Xcode 5


1
อีกตัวอย่างหนึ่งก็คือหากการดำเนินการยังคงอยู่ในผู้สร้างบล็อกและจะไม่ออกเมื่อดำเนินการเสร็จสิ้น +1 เมื่อเขียนดี!
Leo Natan

@LeoNatan เห็นด้วยแม้ว่าโค้ดขนาดสั้นแสดงว่าเป็นตัวแปรในตัวเครื่องซึ่งจะมีการเปิดตัวหากเขาใช้ ARC แต่คุณพูดถูก!
Rob

1
ใช่ฉันแค่ยกตัวอย่างตามที่ OP ร้องขอในคำตอบอื่น
Leo Natan

อย่างไรก็ตาม Xcode 8 มี "กราฟหน่วยความจำดีบั๊ก" ซึ่งเป็นวิธีที่ง่ายยิ่งขึ้นในการค้นหาการอ้างอิงที่แข็งแกร่งไปยังวัตถุที่ไม่ได้วางจำหน่าย ดูstackoverflow.com/questions/30992338/… .
Rob

0

คำอธิบายบางอย่างไม่สนใจเงื่อนไขเกี่ยวกับวัฏจักรการเก็บรักษา [หากกลุ่มวัตถุเชื่อมต่อกันด้วยวงกลมที่มีความสัมพันธ์ที่แน่นหนาพวกมันจะมีชีวิตซึ่งกันและกันแม้ว่าจะไม่มีการอ้างอิงที่แข็งแกร่งจากภายนอกกลุ่ม] สำหรับข้อมูลเพิ่มเติมอ่านเอกสาร


-2

นี่คือวิธีที่คุณสามารถใช้ตนเองภายในบล็อก:

// การเรียกบล็อก

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

NSString* (^checkIfOutsideMethodIsCalled)(*)=^NSString*(id obj)
{
             [obj MethodNameYouWantToCall]; // this is how it will call the object 
            return @"Called";


};
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.