Nắm bắt bản thân mạnh mẽ trong khối này có khả năng dẫn đến một chu kỳ giữ lại


207

Làm thế nào tôi có thể tránh cảnh báo này trong xcode. Đây là đoạn mã:

[player(AVPlayer object) addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
queue:nil usingBlock:^(CMTime time) {
    current+=1;

    if(current==60)
    {
        min+=(current/60);
        current = 0;
    }

    [timerDisp(UILabel) setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];///warning occurs in this line
}];

timerDispmột tài sản trên lớp?
Tim

Có, @property (nonatomic, strong) UILabel * timerDisp;
dùng1845209

2
Cái gì đây: player(AVPlayer object)timerDisp(UILabel)?
Carl Veazey

Trình phát AVPlayer *; UILabel * timerDisp;
dùng1845209

5
Câu hỏi thực sự là làm thế nào để tắt tiếng cảnh báo này mà không có tham chiếu yếu không cần thiết về bản thân, khi bạn biết tham chiếu vòng tròn sẽ bị hỏng (ví dụ: nếu bạn luôn xóa tham chiếu khi yêu cầu mạng kết thúc).
Glenn Maynard

Câu trả lời:


514

Việc nắm bắt selfở đây đang đến với quyền truy cập thuộc tính tiềm ẩn của self.timerDispbạn - bạn không thể tham khảo selfhoặc thuộc tính selftừ trong một khối sẽ được giữ lại mạnh mẽ bởi self.

Bạn có thể khắc phục điều này bằng cách tạo một tham chiếu yếu đến selftrước khi truy cập vào timerDispbên trong khối của mình:

__weak typeof(self) weakSelf = self;
[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                     queue:nil
                                usingBlock:^(CMTime time) {
                                                current+=1;

                                                if(current==60)
                                                {
                                                    min+=(current/60);
                                                    current = 0;
                                                }

                                                 [weakSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
                                            }];

13
Hãy thử sử dụng __unsafe_unretainedthay thế.
Tim

63
Đã giải quyết. sử dụng cái này thay thế: __unsafe_unretained typeof (self) yếuSelf = self; cảm ơn sự giúp đỡ
@Tim

1
Câu trả lời hay, nhưng tôi có một vấn đề nhỏ với bạn khi nói: Bạn không thể tự nói về bản thân hoặc tài sản từ trong một khối sẽ được giữ lại một cách mạnh mẽ. Điều này là không đúng sự thật. Xin vui lòng xem câu trả lời của tôi dưới đây. Tốt hơn để nói, bạn phải hết sức cẩn thận nếu tham khảo về bản thân mình
Chris Suter

8
Tôi không thấy chu kỳ giữ lại trong mã của OP. Khối này không được giữ lại mạnh mẽ bởi self, nó được giữ lại bởi hàng đợi công văn chính. Liệu tôi có sai?
erikprice

3
@erikprice: bạn không sai. Tôi đã giải thích câu hỏi chủ yếu là về lỗi Xcode ("Làm cách nào tôi có thể tránh cảnh báo này trong xcode"), thay vì về sự hiện diện thực tế của chu kỳ giữ lại. Bạn đã đúng khi nói rằng không có chu kỳ giữ lại là điều hiển nhiên chỉ từ đoạn trích OP được cung cấp.
Tim

52
__weak MyClass *self_ = self; // that's enough
self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
    if (!error) {
       [self_ showAlertWithError:error];
    } else {
       self_.items = [NSArray arrayWithArray:receivedItems];
       [self_.tableView reloadData];
    }
};

Và một điều rất quan trọng cần nhớ: không sử dụng các biến thể hiện trực tiếp trong khối, sử dụng nó làm thuộc tính của đối tượng yếu, mẫu:

self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
        if (!error) {
           [self_ showAlertWithError:error];
        } else {
           self_.items = [NSArray arrayWithArray:receivedItems];
           [_tableView reloadData]; // BAD! IT ALSO WILL BRING YOU TO RETAIN LOOP
        }
 };

và đừng quên làm:

- (void)dealloc {
    self.loadingCompletionHandler = NULL;
}

một vấn đề khác có thể xuất hiện nếu bạn sẽ vượt qua bản sao yếu không được giữ lại bởi bất kỳ đối tượng nào:

MyViewController *vcToGo = [[MyViewCOntroller alloc] init];
__weak MyViewController *vcToGo_ = vcToGo;
self.loadingCompletion = ^{
    [vcToGo_ doSomePrecessing];
};

Nếu vcToGonó sẽ được giải quyết và sau đó khối này bị bắn Tôi tin rằng bạn sẽ gặp sự cố với bộ chọn không được nhận dạng vào thùng rác có chứa vcToGo_biến. Hãy cố gắng kiểm soát nó.


3
Đây sẽ là một câu trả lời mạnh mẽ hơn nếu bạn cũng giải thích nó.
Eric J.

43

Phiên bản tốt hơn

__strong typeof(self) strongSelf = weakSelf;

Tạo một tham chiếu mạnh đến phiên bản yếu đó là dòng đầu tiên trong khối của bạn. Nếu bản thân vẫn tồn tại khi khối bắt đầu thực thi và không rơi trở lại con số không, dòng này đảm bảo nó tồn tại trong suốt vòng đời thực thi của khối.

Vì vậy, toàn bộ điều này sẽ như thế này:

// Establish the weak self reference
__weak typeof(self) weakSelf = self;

[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                 queue:nil
                            usingBlock:^(CMTime time) {

    // Establish the strong self reference
    __strong typeof(self) strongSelf = weakSelf;

    if (strongSelf) {
        [strongSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
    } else {
        // self doesn't exist
    }
}];

Tôi đã đọc bài viết này nhiều lần. Đây là một bài viết xuất sắc của Erica Sadun về Cách tránh các vấn đề khi sử dụng Khối và NSNotificationCenter


Cập nhật nhanh:

Ví dụ: trong swift, một phương thức đơn giản với khối thành công sẽ là:

func doSomeThingWithSuccessBlock(success: () -> ()) {
    success()
}

Khi chúng ta gọi phương thức này và cần sử dụng selftrong khối thành công. Chúng tôi sẽ sử dụng các tính năng [weak self]guard let.

    doSomeThingWithSuccessBlock { [weak self] () -> () in
        guard let strongSelf = self else { return }
        strongSelf.gridCollectionView.reloadData()
    }

Cái gọi là điệu nhảy mạnh-yếu này được sử dụng bởi dự án nguồn mở phổ biến Alamofire.

Để biết thêm thông tin, hãy xem hướng dẫn kiểu swift


Điều gì nếu bạn đã làm typeof(self) strongSelf = self;bên ngoài khối (thay vì __weak) sau đó trong khối nói strongSelf = nil;sau khi sử dụng? Tôi không thấy làm thế nào ví dụ của bạn đảm bảo rằng yếu đuối không tồn tại vào thời điểm khối thực thi.
Matt

Để tránh các chu kỳ giữ lại có thể, chúng tôi thiết lập một tham chiếu tự yếu bên ngoài bất kỳ khối nào sử dụng tự trong mã của nó. Theo cách của bạn, bạn phải đảm bảo rằng khối được thực thi. Một khối mã ur khác hiện chịu trách nhiệm giải phóng bộ nhớ ur được giữ lại trước đó.
Warif Akhand Rishi

@Matt mục đích của ví dụ này không phải là làm cho kẻ yếu được giữ lại. Mục đích là, nếu kẻ yếu không phải là con số không, hãy tạo một tài liệu tham khảo mạnh mẽ bên trong khối. Vì vậy, một khi khối bắt đầu thực thi với bản thân, bản thân sẽ không trở thành con số bên trong khối.
Warif Akhand Rishi

15

Trong một câu trả lời khác, Tim nói:

bạn không thể tự đề cập đến bản thân hoặc tài sản từ trong một khối sẽ được giữ lại một cách mạnh mẽ.

Điều này không hoàn toàn đúng. Bạn có thể làm điều này miễn là bạn phá vỡ chu kỳ tại một thời điểm nào đó. Ví dụ: giả sử bạn có một bộ hẹn giờ kích hoạt có một khối giữ lại bản thân và bạn cũng giữ một tham chiếu mạnh mẽ đến bộ định thời gian. Điều này là hoàn toàn tốt nếu bạn luôn biết rằng bạn sẽ phá hủy bộ đếm thời gian tại một số điểm và phá vỡ chu kỳ.

Trong trường hợp của tôi vừa rồi, tôi đã có cảnh báo về mã đã:

[x setY:^{ [x doSomething]; }];

Bây giờ tôi tình cờ biết rằng tiếng kêu sẽ chỉ tạo ra cảnh báo này nếu nó phát hiện ra phương thức bắt đầu với bộ cài đặt (và một trường hợp đặc biệt khác mà tôi sẽ đề cập ở đây). Đối với tôi, tôi biết không có nguy cơ tồn tại vòng lặp giữ, vì vậy tôi đã thay đổi tên phương thức thành Cấm sử dụng: Tất nhiên, điều đó có thể không phù hợp trong mọi trường hợp và thông thường bạn sẽ muốn sử dụng một tham chiếu yếu, nhưng Tôi nghĩ rằng nó đáng chú ý giải pháp của tôi trong trường hợp nó giúp đỡ người khác.


4

Nhiều lần, đây không thực sự là một chu kỳ giữ lại .

Nếu bạn biết rằng nó không phải, bạn không cần phải mang những kẻ yếu đuối vô bổ vào thế giới.

Apple thậm chí còn buộc các cảnh báo này phải cung cấp cho chúng tôi API của chúng UIPageViewController, bao gồm một phương thức đã được đặt (kích hoạt các cảnh báo này như đã đề cập ở nơi khác. và một khối xử lý hoàn thành (trong đó bạn chắc chắn sẽ đề cập đến chính mình).

Dưới đây là một số chỉ thị của trình biên dịch để loại bỏ cảnh báo khỏi một dòng mã đó:

#pragma GCC diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
    [self.pageViewController setViewControllers:@[newViewController] direction:navigationDirection animated:YES completion:^(BOOL finished) {
        // this warning is caused because "setViewControllers" starts with "set…", it's not a problem
        [self doTheThingsIGottaDo:finished touchThePuppetHead:YES];
    }];
#pragma GCC diagnostic pop

1

Thêm hai xu vào việc cải thiện độ chính xác và phong cách. Trong hầu hết các trường hợp, bạn sẽ chỉ sử dụng một hoặc một vài thành viên selftrong khối này, rất có thể chỉ để cập nhật một thanh trượt. Đúc selflà quá mức cần thiết. Thay vào đó, tốt hơn là nên rõ ràng và chỉ chọn các đối tượng mà bạn thực sự cần trong khối. Ví dụ: nếu đó là một ví dụ của UISlider*, giả sử _timeSlider, chỉ cần làm như sau trước khi khai báo khối:

UISlider* __weak slider = _timeSlider;

Sau đó chỉ cần sử dụng sliderbên trong khối. Về mặt kỹ thuật, điều này chính xác hơn vì nó thu hẹp chu kỳ giữ tiềm năng chỉ với đối tượng mà bạn cần chứ không phải tất cả các đối tượng bên trong self.

Ví dụ đầy đủ:

UISlider* __weak slider = _timeSlider;
[_embeddedPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1)
     queue:nil
     usingBlock:^(CMTime time){
        slider.value = time.value/time.timescale;
     }
];

Ngoài ra, rất có thể đối tượng được truyền tới một con trỏ yếu đã là một con trỏ yếu bên trong selfcũng như giảm thiểu hoặc loại bỏ hoàn toàn khả năng của một chu kỳ giữ lại. Trong ví dụ trên, _timeSliderthực sự là một thuộc tính được lưu trữ dưới dạng tham chiếu yếu, ví dụ:

@property (nonatomic, weak) IBOutlet UISlider* timeSlider;

Về kiểu mã hóa, như với C và C ++, các khai báo biến được đọc tốt hơn từ phải sang trái. Khai báo SomeType* __weak variabletheo thứ tự này đọc tự nhiên hơn từ phải sang trái là : variable is a weak pointer to SomeType.


1

Tôi đã chạy vào cảnh báo này gần đây và muốn hiểu nó tốt hơn một chút. Sau một chút thử nghiệm và sai sót, tôi phát hiện ra rằng nó bắt nguồn từ việc có một phương thức bắt đầu bằng "thêm" hoặc "lưu". Mục tiêu C xử lý các tên phương thức bắt đầu bằng "mới", "phân bổ", v.v. như trả về một đối tượng được giữ lại nhưng không đề cập (mà tôi có thể tìm thấy) bất cứ điều gì về "thêm" hoặc "lưu". Tuy nhiên, nếu tôi sử dụng tên phương thức theo cách này:

[self addItemWithCompletionBlock:^(NSError *error) {
            [self done]; }];

Tôi sẽ thấy cảnh báo ở dòng [tự thực hiện]. Tuy nhiên, điều này sẽ không:

[self itemWithCompletionBlock:^(NSError *error) {
    [self done]; }];

Tôi sẽ tiếp tục và sử dụng cách "__weak __typeof (self) yếuSelf = self" để tham chiếu đối tượng của mình nhưng thực sự không muốn phải làm như vậy vì nó sẽ gây nhầm lẫn cho tôi và / hoặc nhà phát triển khác trong tương lai. Tất nhiên, tôi cũng không thể sử dụng "thêm" (hoặc "lưu") nhưng điều đó tệ hơn vì nó làm mất đi ý nghĩa của phương thức.

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.