Câu trả lời ngắn
Thay vì truy cập self
trực tiếp, bạn nên truy cập gián tiếp, từ một tài liệu tham khảo sẽ không được giữ lại. Nếu bạn không sử dụng Đếm tham chiếu tự động (ARC) , bạn có thể thực hiện việc này:
__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
Các __block
biến dấu từ khóa đó có thể thay đổi bên trong các khối (chúng tôi không làm điều đó) nhưng họ cũng không được tự động giữ lại khi khối được giữ lại (trừ khi bạn đang sử dụng ARC). Nếu bạn làm điều này, bạn phải chắc chắn rằng không có gì khác sẽ cố gắng thực thi khối sau khi phiên bản MyDataProcessor được phát hành. (Với cấu trúc mã của bạn, đó không phải là vấn đề.) Đọc thêm về__block
.
Nếu bạn đang sử dụng ARC , ngữ nghĩa của các __block
thay đổi và tham chiếu sẽ được giữ lại, trong trường hợp đó bạn nên khai báo __weak
thay thế.
Câu trả lời dài
Giả sử bạn có mã như thế này:
self.progressBlock = ^(CGFloat percentComplete) {
[self.delegate processingWithProgress:percentComplete];
}
Vấn đề ở đây là bản thân đang giữ một tham chiếu đến khối; trong khi đó, khối phải giữ lại một tham chiếu đến bản thân để tìm nạp thuộc tính ủy nhiệm của nó và gửi cho đại biểu một phương thức. Nếu mọi thứ khác trong ứng dụng của bạn giải phóng tham chiếu đến đối tượng này, thì số giữ lại của nó sẽ bằng 0 (vì khối này đang trỏ đến nó) và khối không làm gì sai (vì đối tượng đang trỏ vào nó) và vì vậy cặp đối tượng sẽ rò rỉ vào đống, chiếm bộ nhớ nhưng mãi mãi không thể truy cập mà không có trình gỡ lỗi. Đáng thương, thật đấy.
Trường hợp đó có thể dễ dàng khắc phục bằng cách làm điều này thay vào đó:
id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
[progressDelegate processingWithProgress:percentComplete];
}
Trong mã này, tự giữ lại khối, khối đang giữ lại đại biểu và không có chu kỳ (có thể nhìn thấy từ đây; đại biểu có thể giữ lại đối tượng của chúng tôi nhưng điều đó nằm ngoài tầm tay của chúng tôi ngay bây giờ). Mã này sẽ không có nguy cơ bị rò rỉ theo cùng một cách, bởi vì giá trị của thuộc tính đại biểu được nắm bắt khi khối được tạo, thay vì tra cứu khi thực thi. Một tác dụng phụ là, nếu bạn thay đổi ủy nhiệm sau khi khối này được tạo, khối sẽ vẫn gửi thông báo cập nhật cho đại biểu cũ. Điều đó có khả năng xảy ra hay không phụ thuộc vào ứng dụng của bạn.
Ngay cả khi bạn bình tĩnh với hành vi đó, bạn vẫn không thể sử dụng thủ thuật đó trong trường hợp của mình:
self.dataProcessor.progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};
Ở đây bạn đang chuyển self
trực tiếp đến đại biểu trong lệnh gọi phương thức, vì vậy bạn phải đưa nó vào đó ở đâu đó. Nếu bạn có quyền kiểm soát định nghĩa của loại khối, điều tốt nhất sẽ là chuyển đại biểu vào khối làm tham số:
self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};
Giải pháp này tránh được chu trình giữ lại và luôn gọi cho đại biểu hiện tại.
Nếu bạn không thể thay đổi khối, bạn có thể đối phó với nó . Lý do chu kỳ giữ lại là một cảnh báo, không phải là lỗi, là vì chúng không nhất thiết phải đánh vần số phận cho ứng dụng của bạn. Nếu MyDataProcessor
có thể giải phóng các khối khi hoạt động hoàn tất, trước khi cha mẹ của nó cố gắng giải phóng nó, chu trình sẽ bị phá vỡ và mọi thứ sẽ được dọn sạch đúng cách. Nếu bạn có thể chắc chắn về điều này, thì điều đúng đắn cần làm là sử dụng một #pragma
để triệt tiêu các cảnh báo cho khối mã đó. (Hoặc sử dụng cờ trình biên dịch cho mỗi tệp. Nhưng không tắt cảnh báo cho toàn bộ dự án.)
Bạn cũng có thể xem xét bằng cách sử dụng một thủ thuật tương tự ở trên, khai báo một tham chiếu yếu hoặc không được lưu ý và sử dụng nó trong khối. Ví dụ:
__weak MyDataProcessor *dp = self; // OK for iOS 5 only
__unsafe_unretained MyDataProcessor *dp = self; // OK for iOS 4.x and up
__block MyDataProcessor *dp = self; // OK if you aren't using ARC
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
Tất cả ba trong số trên sẽ cung cấp cho bạn một tham chiếu mà không giữ lại kết quả, mặc dù tất cả chúng đều hoạt động hơi khác một chút: __weak
sẽ cố gắng không tham chiếu khi đối tượng được phát hành; __unsafe_unretained
sẽ để lại cho bạn một con trỏ không hợp lệ; __block
sẽ thực sự thêm một mức độ gián tiếp khác và cho phép bạn thay đổi giá trị của tham chiếu từ trong khối (không liên quan trong trường hợp này, vì dp
không được sử dụng ở bất kỳ nơi nào khác).
Điều gì tốt nhất sẽ phụ thuộc vào mã bạn có thể thay đổi và những gì bạn không thể. Nhưng hy vọng điều này đã cho bạn một số ý tưởng về cách tiến hành.