rid*_*rid 13 objective-c objective-c-blocks automatic-ref-counting
在编写接受块作为参数的方法时,是否需要执行任何特殊操作,例如在执行块之前将块复制到堆中?例如,如果我有以下方法:
- (void)testWithBlock:(void (^)(NSString *))block {
NSString *testString = @"Test";
block(testString);
}
Run Code Online (Sandbox Code Playgroud)
block在打电话或输入方法之前,我应该做些什么吗?或者上面是使用传入块的正确方法?另外,以下调用方法的方法是正确的,还是应该在传递之前对块执行某些操作?
[object testWithBlock:^(NSString *test){
NSLog(@"[%@]", test);
}];
Run Code Online (Sandbox Code Playgroud)
在哪里做我需要复制块?如果我不使用ARC,这会有什么不同?
Aar*_*man 19
当您收到一个块作为方法参数时,该块可能是在堆栈上创建的原始块,也可能是复制块(堆上的块).据我所知,没有办法说出来.所以一般的经验法则是,如果你要在接收它的方法中执行块,你不需要复制它.如果您打算将该块传递给另一个方法(可能会立即执行或不立即执行),那么您也不需要复制它(接收方法应该复制它,如果它打算保留它).但是,如果您打算以某种方式存储块以供以后执行,则需要复制它.许多人使用的主要示例是作为实例变量保存的某种完成块:
typedef void (^IDBlock) (id);
@implementation MyClass{
IDBlock _completionBlock;
}
Run Code Online (Sandbox Code Playgroud)
但是,如果要将其添加到任何类型的集合类(如NSArray或NSDictionary),还需要复制它.否则,当您尝试稍后执行块时,您将收到错误(很可能是EXC_BAD_ACCESS)或可能是数据损坏.
执行块时,首先测试块是否很重要nil.Objective-c允许您传递nil给block方法参数.如果该块为nil,则在尝试执行时将获得EXC_BAD_ACCESS.幸运的是,这很容易做到.在你的例子中,你写道:
- (void)testWithBlock:(void (^)(NSString *))block {
NSString *testString = @"Test";
if (block) block(testString);
}
Run Code Online (Sandbox Code Playgroud)
复制块有一些性能考虑因素.与在堆栈上创建块相比,将块复制到堆中并非易事.这通常不是什么大不了的事情,但是如果你迭代地使用一个块或者迭代地使用一堆块并在每次执行时复制它们,那么它将产生性能损失.因此,如果您的方法- (void)testWithBlock:(void (^)(NSString *))block;处于某种循环中,那么复制该块可能会损害您的性能,如果您不需要复制它.
复制块所需的另一个地方是,如果您打算自己调用该块(块递归).这并不常见,但如果您打算这样做,则必须复制该块.在这里查看我的问题/答案:Objective-C中的递归块.
最后,如果您要存储块,则需要非常小心创建保留周期.块将保留传递给它的任何对象,如果该对象是实例变量,它将保留实例变量的类(self).我个人喜欢积木并且一直使用它们.但是有一个原因是Apple没有为他们的UIKit类使用/存储块,而是坚持使用目标/动作或委托模式.如果你(创建块的类)保留了接收/复制/存储块的类,并且在该块中引用了自己或任何类实例变量,那么你创建了一个保留周期(classA - > classB - >块 - > classA).这非常容易做到,这是我做过很多次的事情.此外,乐器中的"泄漏"并没有发现它.解决这个问题的方法很简单:只需创建一个临时__weak变量(对于ARC)或__block变量(非ARC),块就不会保留该变量.因此,例如,如果'对象'复制/存储块,则以下将是保留周期:
[object testWithBlock:^(NSString *test){
_iVar = test;
NSLog(@"[%@]", test);
}];
Run Code Online (Sandbox Code Playgroud)
但是,为了解决这个问题(使用ARC):
__weak IVarClass *iVar = _iVar;
[object testWithBlock:^(NSString *test){
iVar = test;
NSLog(@"[%@]", test);
}];
Run Code Online (Sandbox Code Playgroud)
你也可以这样做:
__weak ClassOfSelf _self = self;
[object testWithBlock:^(NSString *test){
_self->_iVar = test;
NSLog(@"[%@]", test);
}];
Run Code Online (Sandbox Code Playgroud)
请注意,许多人不喜欢上述内容,因为他们认为它很脆弱,但它是访问变量的有效方法.
更新 - 如果您尝试使用" - >"直接访问变量,则当前编译器现在会发出警告.出于这个原因(以及安全原因),最好为要访问的变量创建属性.所以_self->_iVar = test;你不会使用:_self.iVar = test;.
更新(更多信息)
通常,最好将接收块的方法视为负责确定是否需要复制块而不是调用者.这是因为接收方法可以是唯一知道块需要保持多长时间或者是否需要复制的方法.您(作为程序员)在编写调用时显然会知道此信息,但如果您在心理上将调用者和接收者视为单独的对象,则调用者会向接收者提供阻止并完成该操作.因此,它不应该知道在块消失后对块做了什么.另一方面,调用者很可能已经复制了块(可能它存储了块并且现在将其移交给另一个方法)但是接收者(也打算存储块)仍然应该复制块(即使已经复制了块).接收方不能知道该块已经被复制,并且它接收的一些块可能被复制而其他块可能不被复制.因此,接收器应该始终复制它打算保留的块吗?合理?这基本上是良好的面向对象设计实践.基本上,任何拥有该信息的人都有责任处理它.
块在Apple的GCD(Grand Central Dispatch)中广泛使用,可轻松实现多线程.通常,在GCD上发送块时,不需要复制块.奇怪的是,这有点违反直觉(如果你考虑的话),因为如果你异步调度一个块,通常创建块的方法将在块执行之前返回,这通常意味着块将过期,因为它是堆栈对象.我不认为GCD将块复制到堆栈(我在某处读取但是再也找不到它),而是我认为线程的生命是通过放在另一个线程来扩展的.
Mike Ash有关于块,GCD和ARC的大量文章,您可能会发现它们很有用:
这一切看起来都不错.但是,您可能需要仔细检查块参数:
@property id myObject;
@property (copy) void (^myBlock)(NSString *);
Run Code Online (Sandbox Code Playgroud)
....
- (void)testWithBlock: (void (^)(NSString *))block
{
NSString *testString = @"Test";
if (block)
{
block(test);
myObject = Block_copy(block);
myBlock = block;
}
}
Run Code Online (Sandbox Code Playgroud)
...
[object testWithBlock: ^(NSString *test)
{
NSLog(@"[%@]", test);
}];
Run Code Online (Sandbox Code Playgroud)
应该没事.我相信他们甚至试图逐步淘汰Block_copy(),但他们还没有.
| 归档时间: |
|
| 查看次数: |
4448 次 |
| 最近记录: |