11

Tôi đang gặp khó khăn khi chuyển đổi một số mã NSOperation thành ARC. Đối tượng hoạt động của tôi sử dụng một khối hoàn thành, mà lần lượt chứa một khối GCD cập nhật giao diện người dùng trên chủ đề chính. Bởi vì tôi tham chiếu đến đối tượng hoạt động của mình từ bên trong khối hoàn thành riêng của nó, tôi đang sử dụng một con trỏ __weak để tránh rò rỉ bộ nhớ. Tuy nhiên, con trỏ đã được đặt thành không theo thời gian mã của tôi chạy.Tham chiếu đối tượng NSOperation trong khối hoàn thành của riêng nó với ARC

Tôi đã thu hẹp nó xuống mẫu mã này. Bất cứ ai biết nơi tôi đã đi sai, và đúng cách để thực hiện điều này?

NSOperationSubclass *operation = [[NSOperationSubclass alloc] init]; 
__weak NSOperationSubclass *weakOperation = operation; 

[operation setCompletionBlock:^{ 
    dispatch_async(dispatch_get_main_queue(), ^{ 

     // fails the check 
     NSAssert(weakOperation != nil, @"pointer is nil"); 

     ... 
    }); 
}]; 
+1

Vâng, những gì đã xảy ra là con trỏ yếu không giữ quyền sở hữu. Nếu không có gì khác giữ biến (và không có), nó sẽ bị xóa. Bạn có chắc là bạn bị rò rỉ nếu bạn sử dụng 'hoạt động'? Nó có vẻ như nó sẽ biến mất khi khối hoàn thành được phát hành, mà nó nên được ngay khi nó được gọi. (Đó có thể là ngây thơ, mặc dù.) –

+0

ARC đã phàn nàn về nó tại thời gian biên dịch. Nếu không có nó tôi đã sử dụng con trỏ hoạt động trực tiếp (và tôi không tin rằng tôi đã bị rò rỉ bộ nhớ). –

+1

Chúc may mắn với điều này. Tôi nghĩ rằng tôi đã đấu tranh chống lại nó trong vài giờ trước khi bỏ cuộc và làm điều gì đó khác. Nhưng đã lâu rồi. :) –

Trả lời

10

Tôi không chắc chắn về điều này, nhưng cách chính xác để làm điều đó là có thể để thêm __block vào biến trong câu hỏi, và sau đó đặt nó là con số không vào cuối của khối để đảm bảo rằng nó là phát hành. See this question.

mã mới của bạn sẽ trông như thế này:

NSOperationSubclass *operation = [[NSOperationSubclass alloc] init]; 
__block NSOperationSubclass *weakOperation = operation; 

[operation setCompletionBlock:^{ 
    dispatch_async(dispatch_get_main_queue(), ^{ 

     // fails the check 
     NSAssert(weakOperation != nil, @"pointer is nil"); 

     ... 
     weakOperation = nil; 
    }); 

}]; 
+3

Bạn đúng là tôi tin. Cảm ơn! –

14

Một lựa chọn khác sẽ là:

NSOperationSubclass *operation = [[NSOperationSubclass alloc] init]; 
__weak NSOperationSubclass *weakOperation = operation; 

[operation setCompletionBlock:^{ 
    NSOperationSubclass *strongOperation = weakOperation; 

    dispatch_async(dispatch_get_main_queue(), ^{ 
     assert(strongOperation != nil); 
     ... 
    }); 
}]; 

[operationQueue addOperation:operation]; 

tôi giả sử bạn cũng có thêm đối tượng hoạt động với một NSOperationQueue. Trong trường hợp đó, hàng đợi đang giữ lại một hoạt động. Nó có lẽ cũng giữ lại nó trong khi thực hiện khối hoàn thành (mặc dù tôi đã không tìm thấy xác nhận chính thức về khối hoàn thành).

Nhưng bên trong khối hoàn thành bạn, một khối khác được tạo. Khối đó sẽ được chạy vào một thời điểm nào đó sau đó, có thể sau khi khối hoàn thành của NSOperations được chạy đến cuối. Khi điều này xảy ra, operation sẽ được giải phóng theo hàng đợi và weakOperation sẽ là nil. Nhưng nếu chúng ta tạo một tham chiếu mạnh khác cho cùng một đối tượng từ khối hoàn thành của hoạt động, chúng ta sẽ đảm bảo operation sẽ tồn tại khi khối thứ hai được chạy và tránh chu kỳ giữ lại vì chúng ta không nắm bắt được khối operation.

Apple cung cấp ví dụ này trong Transitioning to ARC Release Notes xem đoạn mã cuối cùng trong Sử dụng vòng loại trọn đời để tránh chu kỳ tham chiếu mạnh mẽ phần.

+4

+1 Đây là câu trả lời đúng.'NSOperation' vẫn giữ lại khối hoàn thành, vì vậy an toàn khi sử dụng một tham chiếu yếu đến nó trong khối hoàn thành, bởi vì nó được đảm bảo vẫn còn sống. Tuy nhiên, vấn đề của OP là họ đang sử dụng nó trong khối thứ hai, được thực hiện sau đó, và tham chiếu yếu không được đảm bảo để được sống ở đó. Giải pháp đúng là có khối thứ hai có tham chiếu mạnh mẽ đến 'NSOperation' – user102008

4

Câu trả lời được chấp nhận là chính xác. Tuy nhiên không có cần phải weakify hoạt động như iOS 8/Mac OS 10.10:

các trích dẫn từ NSOperation documentation on @completionBlock:

Trong iOS 8 và sau đó và OS X v10.10 và sau đó, tài sản này được thiết lập để nil sau khi khối hoàn thành bắt đầu thực hiện.

Xem thêm this tweet từ Pete Steinberger.