Luôn vượt qua tham chiếu yếu của bản thân vào khối trong ARC?


252

Tôi hơi bối rối về việc sử dụng khối trong Objective-C. Tôi hiện đang sử dụng ARC và tôi có khá nhiều khối trong ứng dụng của mình, hiện tại luôn đề cập đến selfthay vì tham chiếu yếu. Có thể đó là nguyên nhân của các khối này giữ lại selfvà giữ cho nó khỏi bị xử lý? Câu hỏi là, tôi có nên luôn luôn sử dụng một weaktài liệu tham khảo selftrong một khối?

-(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];
}

Quá trình xử lý.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);
    }
}

Nếu bạn muốn có một bài diễn văn chuyên sâu về chủ đề này, hãy đọc dhoerl.wordpress.com/2013/04/23/ trên
David H

Câu trả lời:


721

Nó giúp không tập trung vào stronghoặc weakmột phần của cuộc thảo luận. Thay vào đó tập trung vào phần chu kỳ .

Một chu trình giữ lại là một vòng lặp xảy ra khi Đối tượng A giữ lại Đối tượng B Đối tượng B giữ lại Đối tượng A. Trong tình huống đó, nếu một trong hai đối tượng được giải phóng:

  • Đối tượng A sẽ không bị xử lý vì Đối tượng B có tham chiếu đến nó.
  • Nhưng đối tượng B sẽ không bao giờ bị hủy bỏ miễn là đối tượng A có liên quan đến nó.
  • Nhưng đối tượng A sẽ không bao giờ bị hủy bỏ vì đối tượng B có tham chiếu đến nó.
  • quảng cáo vô hạn

Do đó, hai đối tượng đó sẽ chỉ quanh quẩn trong bộ nhớ cho vòng đời của chương trình mặc dù chúng nên, nếu mọi thứ hoạt động tốt, sẽ bị loại bỏ.

Vì vậy, điều chúng tôi lo lắng là giữ lại các chu kỳ và không có gì về chính các khối tạo ra các chu kỳ này. Đây không phải là một vấn đề, ví dụ:

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

Khối giữ lại self, nhưng selfkhông giữ lại khối. Nếu một hoặc cái khác được phát hành, không có chu kỳ nào được tạo ra và mọi thứ sẽ được giải quyết như bình thường.

Nơi bạn gặp rắc rối là một cái gì đó như:

//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];     
}];

Bây giờ, đối tượng của bạn ( self) có một strongtham chiếu rõ ràng đến khối. Và khối có một tài liệu tham khảo mạnh mẽ ngầmself . Đó là một chu kỳ, và bây giờ không đối tượng nào sẽ được giải quyết đúng cách.

Bởi vì, trong một tình huống như thế này, self theo định nghĩa đã có một strongtham chiếu đến khối, thường dễ giải quyết nhất bằng cách tạo một tham chiếu yếu rõ ràng selfcho khối sử dụng:

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

Nhưng đây không phải là mẫu mặc định mà bạn làm theo khi xử lý các khối gọi đó self! Điều này chỉ nên được sử dụng để phá vỡ những gì sẽ là một chu kỳ giữ lại giữa bản thân và khối. Nếu bạn chấp nhận mô hình này ở mọi nơi, bạn sẽ có nguy cơ chuyển một khối sang thứ gì đó được thực hiện sau khi bị xử lý 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
Tôi không chắc A giữ B, B giữ A sẽ làm một chu kỳ vô hạn. Từ góc độ của số tham chiếu, số tham chiếu của A và B là 1. Điều gì gây ra chu kỳ duy trì cho tình huống này là khi không có nhóm nào khác có tham chiếu mạnh về A và B bên ngoài - điều đó có nghĩa là chúng ta không thể tiếp cận hai đối tượng này (chúng ta không thể kiểm soát A để giải phóng B và ngược lại), do đó, A và B tham chiếu lẫn nhau để giữ cho cả hai tồn tại.
Danyun Liu

@Danyun Mặc dù đúng là chu kỳ giữ lại giữa A và B không thể phục hồi cho đến khi tất cả các tham chiếu khác đến các đối tượng này đã được phát hành, điều đó không làm cho nó trở thành một chu kỳ. Ngược lại, chỉ vì một chu kỳ cụ thể có thể được phục hồi không có nghĩa là không có mã trong mã của bạn. Giữ lại chu kỳ là một mùi của thiết kế xấu.
jemmons

@jemmons Có, chúng ta nên luôn luôn tránh một thiết kế chu trình giữ lại càng nhiều càng tốt.
Danyun Liu

1
@Master Tôi không thể nói được. Nó phụ thuộc hoàn toàn vào việc thực hiện -setCompleteionBlockWithSuccess:failure:phương pháp của bạn . Nhưng nếu paginatorđược sở hữu bởi ViewControllervà các khối này không được gọi sau ViewControllersẽ được phát hành, sử dụng __weaktham chiếu sẽ là bước đi an toàn (vì selfsở hữu thứ sở hữu các khối và vì vậy có khả năng vẫn còn tồn tại khi các khối gọi nó mặc dù họ không giữ lại nó). Nhưng đó là rất nhiều "nếu" s. Nó thực sự phụ thuộc vào những gì nó phải làm.
jemmons

1
@Jai Không, và đây là vấn đề cốt lõi của vấn đề quản lý bộ nhớ với các khối / lần đóng. Các đối tượng được giải quyết khi không có gì sở hữu chúng. MyObjectSomeOtherObjectcả hai đều sở hữu khối. Nhưng bởi vì tham chiếu của khối trở lại MyObjectweak, khối không sở hữu MyObject. Vì vậy, trong khi các khối được đảm bảo để tồn tại càng lâu càng một trong hai MyObject hoặc SomeOtherObject tồn tại, không có đảm bảo rằng MyObjectsẽ tồn tại chừng nào khối không. MyObjectcó thể được giải quyết hoàn toàn và, miễn là SomeOtherObjectvẫn còn tồn tại, khối sẽ vẫn ở đó.
jemmons

26

Bạn không phải luôn luôn sử dụng một tài liệu tham khảo yếu. Nếu khối của bạn không được giữ lại, nhưng được thực thi và sau đó bị loại bỏ, bạn có thể tự chụp mạnh mẽ, vì nó sẽ không tạo ra một chu kỳ giữ lại. Trong một số trường hợp, bạn thậm chí muốn khối giữ bản thân cho đến khi hoàn thành khối để nó không bị phân hủy sớm. Tuy nhiên, nếu bạn chụp khối mạnh mẽ và bên trong tự chụp, nó sẽ tạo ra một chu kỳ giữ lại.


Chà, tôi chỉ thực hiện khối như một cuộc gọi lại và tôi không muốn bản thân bị xử lý gì cả. Nhưng có vẻ như tôi đang tạo chu kỳ giữ lại vì Bộ điều khiển xem trong câu hỏi không được xử lý ...
the_critic

1
Ví dụ: nếu bạn có một mục nút thanh được bộ điều khiển xem giữ lại và bạn tự chụp mạnh mẽ trong khối đó, sẽ có một chu kỳ giữ lại.
Leo Natan

1
@MartinE. Bạn chỉ nên cập nhật câu hỏi của bạn với các mẫu mã. Leo hoàn toàn đúng (+1), rằng điều đó không nhất thiết gây ra chu kỳ tham chiếu mạnh mẽ, nhưng điều đó có thể, tùy thuộc vào cách bạn sử dụng các khối này. Chúng tôi sẽ giúp bạn dễ dàng hơn nếu bạn cung cấp đoạn mã.
Cướp

@LeoNatan Tôi hiểu khái niệm về chu kỳ giữ lại, nhưng tôi không chắc chắn điều gì xảy ra trong các khối, vì vậy điều đó làm tôi bối rối một chút
the_critic 17/11/13

Trong đoạn mã trên, cá thể tự của bạn sẽ được giải phóng khi hoạt động kết thúc và khối được giải phóng. Bạn nên đọc về cách các khối hoạt động và những gì và khi chúng nắm bắt trong phạm vi của chúng.
Leo Natan

26

Tôi hoàn toàn đồng ý với @jemmons:

Nhưng đây không phải là mẫu mặc định mà bạn làm theo khi xử lý các khối tự gọi! Điều này chỉ nên được sử dụng để phá vỡ những gì sẽ là một chu kỳ giữ lại giữa bản thân và khối. Nếu bạn chấp nhận mô hình này ở mọi nơi, bạn sẽ có nguy cơ chuyển một khối sang thứ gì đó được thực thi sau khi tự giải quyết.

//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];
}];

Để khắc phục vấn đề này, người ta có thể định nghĩa một tham chiếu mạnh mẽ weakSelfbên trong khối:

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

3
Sẽ không mạnh mẽ làm tăng số lượng tham chiếu cho yếu. Do đó tạo ra một chu kỳ giữ lại?
mskw

12
Giữ lại chu kỳ chỉ quan trọng nếu chúng tồn tại ở trạng thái tĩnh của đối tượng. Trong khi mã đang thực thi và trạng thái của nó là thông lượng, thì nhiều và có thể giữ lại dự phòng là tốt. Dù sao liên quan đến mô hình này, nắm bắt được một tài liệu tham khảo mạnh mẽ, không làm gì cho trường hợp tự giải quyết trước khi khối chạy, điều đó vẫn có thể xảy ra. Nó không đảm bảo bản thân không bị xử lý trong khi thực hiện khối. Điều này quan trọng nếu khối không hoạt động async chính nó đưa ra một cửa sổ cho điều đó xảy ra.
Pierre Houston

@smallduck, lời giải thích của bạn rất hay. Bây giờ tôi hiểu điều này tốt hơn. Sách không bao gồm điều này, cảm ơn.
Huibin Zhang

5
Đây không phải là một ví dụ hay về strongSelf, bởi vì sự bổ sung rõ ràng của strongSelf chính xác là những gì thời gian chạy sẽ làm: dù sao trên dòng doS Something, một tham chiếu mạnh được thực hiện trong suốt thời gian của lệnh gọi phương thức. Nếu yếu thế đã bị vô hiệu hóa, ref mạnh mẽ là không và cuộc gọi phương thức là không có. Nơi strongSelf giúp là nếu bạn có một loạt các hoạt động, hoặc truy cập vào một trường thành viên ( ->), nơi bạn muốn đảm bảo bạn thực sự có một tham chiếu hợp lệ và giữ nó liên tục trên toàn bộ các hoạt động, ví dụ nhưif ( strongSelf ) { /* several operations */ }
Ethan

19

Như Leo chỉ ra, mã bạn đã thêm vào câu hỏi của mình sẽ không đề xuất chu kỳ tham chiếu mạnh (hay còn gọi là chu trình giữ lại). Một vấn đề liên quan đến hoạt động có thể gây ra chu kỳ tham chiếu mạnh sẽ là nếu hoạt động không được phát hành. Mặc dù đoạn mã của bạn gợi ý rằng bạn chưa xác định hoạt động của mình là đồng thời, nhưng nếu bạn có, nó sẽ không được phát hành nếu bạn không bao giờ đăng isFinishedhoặc nếu bạn có phụ thuộc vòng tròn hoặc đại loại như thế. Và nếu hoạt động không được phát hành, bộ điều khiển xem cũng sẽ không được phát hành. Tôi sẽ đề nghị thêm một điểm dừng hoặc NSLogtrong deallocphương thức hoạt động của bạn và xác nhận rằng nó sẽ được gọi.

Bạn đã nói:

Tôi hiểu khái niệm về chu kỳ giữ lại, nhưng tôi không chắc chắn điều gì xảy ra trong các khối, vì vậy điều đó làm tôi bối rối một chút

Các vấn đề về chu trình giữ lại (chu trình tham chiếu mạnh) xảy ra với các khối cũng giống như các sự cố về chu trình giữ lại mà bạn quen thuộc. Một khối sẽ duy trì các tham chiếu mạnh đến bất kỳ đối tượng nào xuất hiện trong khối và nó sẽ không giải phóng các tham chiếu mạnh đó cho đến khi chính khối đó được giải phóng. Do đó, nếu tham chiếu khối self, hoặc thậm chí chỉ tham chiếu một biến thể hiện của self, sẽ duy trì tham chiếu mạnh đến tự, điều đó không được giải quyết cho đến khi khối được giải phóng (hoặc trong trường hợp này, cho đến khi NSOperationlớp con được phát hành.

Để biết thêm thông tin, hãy xem phần Tránh chu kỳ tham chiếu mạnh khi chụp phần tự lập trình với Objective-C: Làm việc với tài liệu Khối .

Nếu bộ điều khiển xem của bạn vẫn chưa được phát hành, bạn chỉ cần xác định vị trí của tham chiếu mạnh chưa được giải quyết (giả sử bạn đã xác nhận rằng NSOperationnó sẽ bị hủy). Một ví dụ phổ biến là việc sử dụng lặp lại NSTimer. Hoặc một số tùy chỉnh delegatehoặc đối tượng khác đang duy trì sai một strongtài liệu tham khảo. Bạn thường có thể sử dụng Dụng cụ để theo dõi nơi các đối tượng đang nhận được tài liệu tham khảo mạnh mẽ của họ, ví dụ:

ghi số tham chiếu trong Xcode 6

Hoặc trong Xcode 5:

ghi số tham chiếu trong Xcode 5


1
Một ví dụ khác là nếu thao tác được giữ lại trong trình tạo khối và không được phát hành sau khi kết thúc. +1 trên viết đẹp lên!
Leo Natan

@LeoNatan Đồng ý, mặc dù đoạn mã đại diện cho nó như một biến cục bộ sẽ được phát hành nếu anh ta sử dụng ARC. Nhưng bạn hoàn toàn đúng!
Cướp

1
Vâng, tôi chỉ đưa ra một ví dụ, như OP yêu cầu trong câu trả lời khác.
Leo Natan

Nhân tiện, Xcode 8 có "Đồ thị bộ nhớ gỡ lỗi", đây là cách thậm chí còn dễ dàng hơn để tìm các tham chiếu mạnh đến các đối tượng chưa được phát hành. Xem stackoverflow.com/questions/30992338/ cấp .
Cướp

0

Một số giải thích bỏ qua một điều kiện về chu kỳ giữ lại [Nếu một nhóm đối tượng được kết nối bằng một vòng tròn có mối quan hệ bền chặt, chúng sẽ giữ cho nhau tồn tại ngay cả khi không có tài liệu tham khảo mạnh từ bên ngoài nhóm.] Để biết thêm thông tin, hãy đọc tài liệu


-2

Đây là cách bạn có thể sử dụng bản thân bên trong khối:

// gọi khối

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

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


};
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.