2012-06-12 37 views
6

Từ Transitioning to ARC Release NotesTại sao chúng ta phải đặt biến __block thành nil?

Qualifiers Sử dụng Lifetime để Tránh Cycles tham khảo mạnh

Bạn có thể sử dụng vòng đời để tránh chu kỳ tài liệu tham khảo mạnh mẽ. Đối với ví dụ , thông thường nếu bạn có biểu đồ các đối tượng được sắp xếp theo thứ tự cha mẹ và con bố mẹ và cha mẹ cần phải tham chiếu đến con cái của họ và ngược lại, sau đó bạn thực hiện mối quan hệ cha-con và mạnh mẽ và mối quan hệ minh bạch yếu. Các tình huống khác có thể nhiều hơn tinh tế, đặc biệt khi chúng liên quan đến các đối tượng khối.

Trong chế độ đếm tham chiếu thủ công, __block id x; có tác dụng không giữ lại x. Ở chế độ ARC, __block id x; mặc định để giữ lại x (chỉ giống như tất cả các giá trị khác). Để có chế độ đếm tham chiếu thủ công hoạt động trong ARC, bạn có thể sử dụng __unsafe_unretained __block id x;. Như tên __unsafe_unretained ngụ ý, tuy nhiên, có một biến số không giữ lại là là nguy hiểm (vì nó có thể treo lơ lửng) và là do đó không khuyến khích. Hai tùy chọn tốt hơn là sử dụng __weak (nếu bạn không cần hỗ trợ iOS 4 hoặc OS X v10.6) hoặc đặt giá trị __block thành nil để phá vỡ chu kỳ lưu giữ.

Được rồi, vậy điều gì khác biệt về biến số __block?

Tại sao đặt thành nil tại đây? Biến số __block có được giữ lại hai lần không? Ai giữ tất cả các tài liệu tham khảo? Khối? Heap? Ngăn xếp? Chủ đề? Cái gì?

Đoạn mã sau minh họa vấn đề này bằng cách sử dụng mẫu thỉnh thoảng được sử dụng trong tính toán tham chiếu thủ công.

MyViewController *myController = [[MyViewController alloc] init…]; 

// ... 

myController.completionHandler = ^(NSInteger result) { 
    [myController dismissViewControllerAnimated:YES completion:nil]; 
}; 

[self presentViewController:myController animated:YES completion:^{ 
    [myController release]; 
}]; 

Như đã trình bày, thay vào đó, bạn có thể sử dụng khuôn khổ vòng loại __block và đặt biến MyController để nil trong xử lý hoàn thành:

MyViewController * __block myController = [[MyViewController alloc] init…]; //Why use __block. my controller is not changed at all 

// ... 

myController.completionHandler = ^(NSInteger result) { 
    [myController dismissViewControllerAnimated:YES completion:nil]; 

    myController = nil; //Why set to nil here? Is __block variable retained twice? Who hold all the reference? The block? The heap? The stack? The thread? The what? 
}; 

Cũng tại sao myController không được đặt để nil bởi trình biên dịch. Tại sao chúng ta phải làm như vậy? Dường như là loại trình biên dịch biết khi nào myController sẽ không còn được sử dụng nữa, đó là khi khối hết hạn.

Trả lời

14

Khi bạn có mã của mẫu này:

object.block = ^{ 
    // reference object from inside the block 
    [object someMethodOrProperty]; 
}; 

object sẽ giữ lại hoặc sao chép khối bạn cung cấp cho nó. Nhưng bản thân khối cũng sẽ giữ lại object vì nó được tham chiếu mạnh mẽ từ bên trong khối. Đây là chu kỳ giữ lại. Ngay cả sau khi khối đã thực hiện xong, chu trình tham chiếu vẫn tồn tại và cả đối tượng lẫn khối cũng không thể được phân phối lại. Hãy nhớ rằng một khối có thể được gọi nhiều lần, do đó, nó không thể quên tất cả các biến mà nó tham chiếu sau khi nó đã thực hiện xong một lần.

Để ngắt chu kỳ này, bạn có thể xác định object thành biến số __block, cho phép bạn thay đổi giá trị của nó từ bên trong khối, ví dụ:thay đổi nó để nil để phá vỡ chu kỳ:

__block id object = ...; 
object.block = ^{ 
    // reference object from inside the block 
    [object someMethodOrProperty]; 

    object = nil; 
    // At this point, the block no longer retains object, so the cycle is broken 
}; 

Khi chúng ta gán object-nil vào cuối khối, khối sẽ không còn giữ được object và duy trì chu kỳ là bị hỏng. Điều này cho phép cả hai đối tượng được deallocated.

Một ví dụ cụ thể về điều này là với thuộc tính completionBlock của NSOperation. Nếu bạn sử dụng completionBlock để truy cập kết quả của một hoạt động, bạn cần phải phá vỡ chu kỳ được tạo giữ:

__block NSOperation *op = [self operationForProcessingSomeData]; 
op.completionBlock = ^{ 
    // since we strongly reference op here, a retain cycle is created 
    [self operationFinishedWithData:op.processedData]; 

    // break the retain cycle! 
    op = nil; 
} 

Như các tài liệu mô tả, có một số kỹ thuật khác mà bạn cũng có thể sử dụng để phá vỡ những duy trì chu kỳ . Ví dụ, bạn sẽ cần phải sử dụng một kỹ thuật khác trong mã không phải ARC hơn là bạn sẽ có trong mã ARC.

+0

"Nhưng bản thân khối cũng sẽ giữ lại đối tượng vì nó được tham chiếu mạnh mẽ từ bên trong khối." Tại sao? Đóng cửa. –

+0

Làm thế nào để thêm __block tạo ra bất kỳ sự khác biệt nào? –

+0

Khi một khối chụp một con trỏ tới đối tượng mục tiêu-c, đối tượng đó sẽ được giữ lại trừ khi bạn sử dụng '__weak' hoặc' __unsafe_unretained' (hoặc '__block' trong mã không phải ARC). –

0

Tôi thích giải pháp này

typeof(self) __weak weakSelf = self; 
self.rotationBlock = ^{ 
    typeof (weakSelf) __strong self = weakSelf; 

    [self yourCodeThatReferenceSelf]; 
}; 

gì xảy ra là các khối sẽ chụp tự như một yếu tham khảo và sẽ có không giữ lại chu kỳ. tự bên trong khối sau đó được định nghĩa lại là __strong self = weakSelf trước khi mã của bạn chạy. Điều này ngăn tự thoát khỏi khi khối của bạn chạy.

Các vấn đề liên quan