Khi bạn nhận được khối dưới dạng tham số phương pháp, khối đó có thể là bản gốc đã được tạo trên ngăn xếp hoặc nó có thể được sao chép (một khối trên heap). Theo như tôi biết, không có cách nào để nói. Vì vậy, quy tắc chung của ngón tay cái là nếu bạn định thực thi khối trong phương thức nhận nó, bạn không cần phải sao chép nó. Nếu bạn dự định chuyển khối đó sang phương thức khác (có thể hoặc không thể thực thi nó ngay lập tức) thì bạn cũng không cần sao chép nó (phương thức nhận sẽ sao chép nó nếu nó có ý định giữ nó). Tuy nhiên, nếu bạn dự định lưu trữ khối theo bất kỳ cách nào cho một số nơi để thực hiện sau này, bạn cần phải sao chép nó.Ví dụ chính nhiều người sử dụng là một số loại khối hoàn thành tổ chức như là một biến Ví dụ:
typedef void (^IDBlock) (id);
@implementation MyClass{
IDBlock _completionBlock;
}
Tuy nhiên, bạn cũng cần phải sao chép nó nếu bạn đang đi để thêm nó vào bất kỳ loại lớp bộ sưu tập, giống như một NSArray hoặc NSDictionary. Nếu không, bạn sẽ gặp lỗi (rất có thể là EXC_BAD_ACCESS) hoặc có thể là hỏng dữ liệu khi bạn cố gắng thực thi khối sau đó.
Khi bạn thực hiện một khối, điều quan trọng là phải kiểm tra trước tiên nếu khối là nil
. Mục tiêu-c sẽ cho phép bạn vượt qua nil
để tham số phương thức chặn. Nếu khối đó là 0, bạn sẽ nhận được EXC_BAD_ACCESS khi bạn cố thực hiện nó. May mắn thay điều này là dễ dàng để làm. Trong ví dụ của bạn, bạn sẽ viết:
- (void)testWithBlock:(void (^)(NSString *))block {
NSString *testString = @"Test";
if (block) block(testString);
}
Có những cân nhắc về hiệu suất trong các khối sao chép. So với việc tạo một khối trên ngăn xếp, sao chép một khối vào heap không phải là tầm thường. Nó không phải là một thỏa thuận lớn nói chung, nhưng nếu bạn đang sử dụng một khối lặp đi lặp lại hoặc sử dụng một loạt các khối lặp đi lặp lại và sao chép chúng trên mỗi thực thi, nó sẽ tạo ra một hit hiệu suất. Vì vậy, nếu phương pháp của bạn - (void)testWithBlock:(void (^)(NSString *))block;
là trong một số loại vòng lặp, sao chép khối đó có thể làm hỏng hiệu suất của bạn nếu bạn không cần phải sao chép nó.
Một địa điểm khác mà bạn cần sao chép một khối là nếu bạn có ý định gọi khối đó trong chính nó (chặn đệ quy). Đây không phải là tất cả những gì phổ biến, nhưng bạn phải sao chép các khối nếu bạn có ý định làm điều này. Xem câu hỏi/câu trả lời của tôi trên SO tại đây: Recursive Blocks In Objective-C.
Cuối cùng, nếu bạn định lưu trữ một khối, bạn cần phải thực sự cẩn thận về việc tạo các chu kỳ lưu giữ. Các khối sẽ giữ lại bất kỳ đối tượng nào được truyền vào nó, và nếu đối tượng đó là một biến cá thể, nó sẽ giữ lại lớp của biến cá thể (self). Cá nhân tôi yêu thích các khối và sử dụng chúng mọi lúc. Nhưng có một lý do mà Apple không sử dụng/lưu trữ các khối cho các lớp UIKit của họ và thay vào đó gắn bó với một mục tiêu/hành động hoặc mẫu đại biểu. Nếu bạn (lớp tạo khối) đang giữ lại lớp đang nhận/sao chép/lưu trữ khối, và trong khối đó bạn tham chiếu hoặc biến thể cá thể của bất kỳ lớp nào, bạn đã tạo một chu trình giữ lại (classA -> classB - > block -> classA). Điều này rất dễ làm, và đó là điều tôi đã làm quá nhiều lần. Hơn nữa, "Rò rỉ" trong dụng cụ không bắt được nó. Phương pháp để giải quyết vấn đề này thật dễ dàng: chỉ cần tạo biến số __weak
tạm thời (đối với ARC) hoặc biến số __block
(không phải ARC) và khối sẽ không giữ lại biến đó. Vì vậy, ví dụ, sau đây sẽ là một giữ lại chu kỳ nếu 'đối tượng' copies/cửa hàng khối:
[object testWithBlock:^(NSString *test){
_iVar = test;
NSLog(@"[%@]", test);
}];
Tuy nhiên, để khắc phục điều này (sử dụng ARC):
__weak IVarClass *iVar = _iVar;
[object testWithBlock:^(NSString *test){
iVar = test;
NSLog(@"[%@]", test);
}];
Bạn cũng có thể làm một cái gì đó như thế này:
__weak ClassOfSelf _self = self;
[object testWithBlock:^(NSString *test){
_self->_iVar = test;
NSLog(@"[%@]", test);
}];
Lưu ý rằng nhiều người không thích ở trên, vì họ coi đó là mong manh, nhưng nó là một cách hợp lệ truy cập vào các biến. Cập nhật - Trình biên dịch hiện tại cảnh báo nếu bạn cố gắng truy cập trực tiếp biến bằng cách sử dụng '->'. Vì lý do này (cũng như lý do an toàn), tốt nhất bạn nên tạo thuộc tính cho các biến mà bạn muốn truy cập. Vì vậy, thay vì _self->_iVar = test;
bạn sẽ sử dụng: _self.iVar = test;
.
UPDATE (thêm thông tin)
Nói chung, nó là tốt nhất để xem xét các phương pháp tiếp nhận khối như chịu trách nhiệm về việc xác định khối cần phải được sao chép chứ không phải là người gọi. Điều này là do phương thức tiếp nhận có thể là phương thức duy nhất biết được khối cần lưu giữ trong bao lâu hoặc nếu nó cần được sao chép. Bạn (với tư cách là lập trình viên) rõ ràng sẽ biết thông tin này khi bạn viết cuộc gọi, nhưng nếu bạn xem xét người gọi và người nhận ở các đối tượng riêng biệt, người gọi sẽ cung cấp cho người nhận khối và được thực hiện với nó. Vì vậy, nó không cần phải biết những gì được thực hiện với khối sau khi nó đã biến mất. Mặt khác, có thể người gọi đã sao chép khối (có thể nó đã lưu trữ khối và hiện đang chuyển nó sang phương thức khác) nhưng người nhận (người cũng dự định lưu trữ khối) vẫn nên sao chép khối (mặc dù khối đã được sao chép). Người nhận không thể biết rằng khối đã được sao chép, và một số khối nó nhận được có thể được sao chép trong khi những người khác có thể không được. Vì vậy, người nhận nên luôn luôn sao chép một khối nó dự định giữ xung quanh? Có lý? Điều này về cơ bản là thực hành thiết kế hướng đối tượng tốt. Về cơ bản, bất cứ ai có thông tin đều chịu trách nhiệm xử lý nó.
Các khối được sử dụng rộng rãi trong GCD của Apple (Grand Central Dispatch) để dễ dàng bật đa luồng. Nói chung, bạn không cần phải sao chép một khối khi bạn gửi nó trên GCD. Thật kỳ lạ, điều này hơi phản trực giác (nếu bạn nghĩ về nó) bởi vì nếu bạn gửi một khối không đồng bộ, thường là phương thức mà khối được tạo ra sẽ trở lại trước khi khối đã thực hiện, điều này có nghĩa là khối sẽ hết hạn vì nó một đối tượng ngăn xếp. Tôi không nghĩ rằng GCD sao chép khối vào ngăn xếp (tôi đọc ở đâu đó nhưng không thể tìm thấy nó một lần nữa), thay vào đó tôi nghĩ rằng cuộc sống của các chủ đề được mở rộng bằng cách được đặt trên một sợi khác.
Mike Ash cũng có bài viết sâu rộng về khối, GCD và ARC mà bạn có thể tìm thấy hữu ích:
Bạn đang sử dụng ARC? –
@pcperini, vâng. – rid