当您收到一个块作为方法参数时,该块可能是在堆栈上创建的原始块,也可能是副本(堆上的一个块)。据我所知,没有办法说。所以一般的经验法则是,如果你要在接收它的方法中执行块,你不需要复制它。如果您打算将该块传递给另一个方法(可能会或可能不会立即执行它),那么您也不需要复制它(如果它打算保留它,接收方法应该复制它)。但是,如果您打算以任何方式将块存储到某个地方以供以后执行,则需要复制它。许多人使用的主要示例是某种作为实例变量保存的完成块:
typedef void (^IDBlock) (id);
@implementation MyClass{
IDBlock _completionBlock;
}
但是,如果要将它添加到任何类型的集合类中,例如 NSArray 或 NSDictionary,则还需要复制它。否则,当您稍后尝试执行该块时,您将收到错误(很可能是 EXC_BAD_ACCESS)或数据损坏。
执行块时,首先测试块是否为nil
. Objective-c 将允许您传递nil
给块方法参数。如果那个块是 nil,当你尝试执行它时你会得到 EXC_BAD_ACCESS。幸运的是,这很容易做到。在您的示例中,您将编写:
- (void)testWithBlock:(void (^)(NSString *))block {
NSString *testString = @"Test";
if (block) block(testString);
}
复制块有性能考虑。与在堆栈上创建块相比,将块复制到堆中并非易事。一般来说,这不是什么大问题,但如果你迭代地使用一个块或迭代地使用一堆块并在每次执行时复制它们,它会造成性能损失。因此,如果您的方法- (void)testWithBlock:(void (^)(NSString *))block;
处于某种循环中,如果您不需要复制该块,则复制该块可能会损害您的性能。
您需要复制块的另一个地方是如果您打算调用该块本身(块递归)。这并不常见,但如果您打算这样做,则必须复制该块。请在此处查看我关于 SO 的问题/答案:Recursive Blocks In Objective-C。
最后,如果你要存储一个块,你需要非常小心地创建保留周期。块将保留传递给它的任何对象,如果该对象是实例变量,它将保留实例变量的类(self)。我个人喜欢积木并一直使用它们。但是,Apple 不为他们的 UIKit 类使用/存储块,而是坚持使用目标/动作或委托模式是有原因的。如果您(创建块的类)保留正在接收/复制/存储块的类,并且在该块中您引用自己或任何类实例变量,则您创建了一个保留循环(classA -> classB - > 块 -> A 类)。这非常容易做到,而且我已经做过太多次了。此外,“泄密” 在 Instruments 中没有抓住它。解决这个问题的方法很简单:只需创建一个临时的__weak
变量(对于 ARC)或__block
变量(非 ARC),并且块不会保留该变量。因此,例如,如果“对象”复制/存储块,则以下将是一个保留周期:
[object testWithBlock:^(NSString *test){
_iVar = test;
NSLog(@"[%@]", test);
}];
但是,要解决这个问题(使用 ARC):
__weak IVarClass *iVar = _iVar;
[object testWithBlock:^(NSString *test){
iVar = test;
NSLog(@"[%@]", test);
}];
你也可以这样做:
__weak ClassOfSelf _self = self;
[object testWithBlock:^(NSString *test){
_self->_iVar = test;
NSLog(@"[%@]", test);
}];
请注意,许多人不喜欢上面的方法,因为他们认为它很脆弱,但它是访问变量的有效方式。
更新- 如果您尝试使用“->”直接访问变量,当前编译器现在会发出警告。出于这个原因(以及安全原因),最好为要访问的变量创建一个属性。因此,_self->_iVar = test;
您可以使用:_self.iVar = test;
。
更新(更多信息)
通常,最好将接收块的方法视为负责确定是否需要复制块,而不是调用者。这是因为接收方法可能是唯一知道块需要保持多长时间或是否需要复制的方法。您(作为程序员)在编写调用时显然会知道这些信息,但如果您在不同的对象中考虑调用者和接收者,调用者将块交给接收者并完成它。因此,它不需要知道在块消失后对块做了什么。另一方面,它' 很可能调用者可能已经复制了块(也许它存储了块并且现在将它交给另一个方法)但是接收者(也打算存储块)仍然应该复制块(即使块正如已经复制的那样)。接收者无法知道该块已经被复制,它接收到的一些块可能被复制,而其他块可能没有。因此接收者应该总是复制一个它打算保留的块?说得通?这本质上是良好的面向对象设计实践。基本上,谁拥有信息,谁就负责处理它。因此接收者应该总是复制一个它打算保留的块?说得通?这本质上是良好的面向对象设计实践。基本上,谁拥有信息,谁就负责处理它。因此接收者应该总是复制一个它打算保留的块?说得通?这本质上是良好的面向对象设计实践。基本上,谁拥有信息,谁就负责处理它。
块在 Apple 的 GCD(Grand Central Dispatch)中被广泛使用,以轻松启用多线程。一般来说,当你在 GCD 上分发一个块时,你不需要复制它。奇怪的是,这有点违反直觉(如果你考虑一下的话),因为如果你异步调度一个块,通常创建块的方法会在块执行之前返回,这通常意味着块会过期,因为它是堆栈对象。我不认为 GCD 将块复制到堆栈中(我在某处读过,但无法再次找到它),相反,我认为线程的寿命可以通过放在另一个线程上来延长。
Mike Ash 有大量关于块、GCD 和 ARC 的文章,您可能会发现它们很有用: