在Objective-C中传递块 [英] Passing blocks in Objective-C

查看:65
本文介绍了在Objective-C中传递块的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

编写一个接受块作为参数的方法时,我是否需要做一些特殊的事情,例如在执行块之前将其复制到堆中?例如,如果我有以下方法:

When writing a method that accepts a block as an argument, do I need to do anything special such as copying the block to the heap before executing it? For example, if I had the following method:

- (void)testWithBlock:(void (^)(NSString *))block {
    NSString *testString = @"Test";
    block(testString);
}

在调用block之前或进入方法时,我应该对它进行任何操作吗?还是以上是使用传入块的正确方法?另外,以下调用方法的方法是否正确,还是应该在传递块之前对块做些事情?

Should I do anything with block before calling it, or when entering the method? Or is the above the correct way of using the passed-in block? Also, is the following way of calling the method correct, or should I do something with the block before passing it?

[object testWithBlock:^(NSString *test){
    NSLog(@"[%@]", test);
}];

我需要在哪里复制 ?如果我不使用ARC,这会有什么不同?

Where do I need to copy the block? And how would this have been different if I wasn't using ARC?

推荐答案

当您收到一个块作为方法参数时,该块可能是在堆栈上创建的原始块,也可能是它的副本(堆).据我所知,还没有办法说出来.因此,一般的经验法则是,如果要在接收该块的方法中执行该块,则无需复制它.如果您打算将该块传递给另一个方法(可能会立即执行,也可能不会立即执行),那么您也不需要复制它(如果接收方打算保留它,则应该复制它).但是,如果打算以任何方式将块存储在某个位置以供以后执行,则需要复制它.许多人使用的主要示例是作为实例变量保存的某种完成块:

When you receive a block as a method parameter, that block could be the original that had been created on the stack or it could be copy (a block on the heap). As far as I'm aware, there's no way tell. So the general rule of thumb is if you're going to execute the block within the method that's receiving it, you don't need to copy it. If you intend to pass that block to another method (which may or may not execute it immediately) then you also don't need to copy it (the receiving method should copy it if it intends to keep it around). However, if you intend to store the block in any way for some place for later execution, you need to copy it. The primary example many people use is some sort of completion block held as an instance variable:

typedef void (^IDBlock) (id);
@implementation MyClass{
    IDBlock _completionBlock;
}

但是,如果要将其添加到任何类型的集合类(如NSArray或NSDictionary)中,也需要将其复制.否则,当您稍后尝试执行该块时,将会得到错误(很可能是EXC_BAD_ACCESS)或数据损坏.

However, you also need to copy it if you're going to add it to any kind of collection class, like an NSArray or NSDictionary. Otherwise, you'll get errors (most likely EXC_BAD_ACCESS) or possibly data corruption when you try to execute the block later.

执行一个块时,首先测试该块是否为nil很重要.使用Objective-c,您可以将nil传递给块方法参数.如果该块为nil,则在尝试执行它时会得到EXC_BAD_ACCESS.幸运的是,这很容易做到.在您的示例中,您将编写:

When you execute a block, it's important to test first if the block is nil. Objective-c will allow you to pass nil to a block method parameter. If that block is nil, you'll get EXC_BAD_ACCESS when you try to execute it. Luckily this is easy to do. In your example, you'd write:

- (void)testWithBlock:(void (^)(NSString *))block {
    NSString *testString = @"Test";
    if (block) block(testString);
}

在复制块中有性能注意事项.与在堆栈上创建一个块相比,将一个块复制到堆上并非易事.一般而言,这不是什么大问题,但是如果您迭代使用一个块或迭代使用一堆块并在每次执行时复制它们,则会造成性能上的损失.因此,如果您的方法- (void)testWithBlock:(void (^)(NSString *))block;处于某种循环中,那么如果您不需要复制该块,则可能会损害您的性能.

There are performance considerations in copying blocks. Compared to creating a block on the stack, copying a block to the heap is not trivial. It's not a major deal in general, but if you're using a block iteratively or using a bunch of blocks iteratively and copying them on each execution, it'll create a performance hit. So if your method - (void)testWithBlock:(void (^)(NSString *))block; were in some kind of loop, copying that block might hurt your performance if you don't need to copy it.

需要复制块的另一个地方是,如果您打算自己调用该块(块递归).这不是很常见,但是如果您打算这样做,则必须复制该块.在此处查看我对SO的问题/答案: Objective-C中的递归块.

Another place you need to copy a block is if you intend on calling that block in itself (block recursion). This isn't all that common, but you must copy the block if you intend to do this. See my question/answer on SO here: Recursive Blocks In Objective-C.

最后,如果要存储一个块,则在创建保留周期时需要非常小心.块将保留传递给它的任何对象,并且如果该对象是实例变量,它将保留实例变量的类(自身).我个人很喜欢积木,并一直使用它们.但是,有一个原因是Apple不为其UIKit类使用/存储块,而是坚持使用目标/操作或委托模式.如果您(创建该块的类)保留了正在接收/复制/存储该块的类,并且在该块中引用了您自己或ANY类实例变量,那么您已经创建了一个保留周期(classA-> classB- >块-> classA).这非常容易做到,这是我做过很多次了.而且,Instruments中的泄漏"无法捕捉到它.解决此问题的方法很简单:只需创建一个临时__weak变量(用于ARC)或__block变量(非ARC),该块就不会保留该变量.因此,例如,如果对象"复制/存储该块,则以下为保留周期:

Finally, if you're going to store a block you need to be really careful about creating retain cycles. Blocks will retain any object passed into it, and if that object is an instance variable, it will retain the instance variable's class (self). I personally love blocks and use them all the time. But there's a reason Apple doesn't use/store blocks for their UIKit classes and instead stick with either a target/action or delegate pattern. If you (the class creating the block) are retaining the class that is receiving/copying/storing the block, and in that block you reference either yourself or ANY class instance variable, you've created a retain cycle (classA -> classB -> block -> classA). This is remarkably easy to do, and it's something I've done too many times. Moreover, "Leaks" in Instruments doesn't catch it. The method to get around this is easy: simply create a temporary __weak variable (for ARC) or __block variable (non-ARC) and the block won't retain that variable. So,for example, the following would be a retain cycle if the 'object' copies/stores the block:

[object testWithBlock:^(NSString *test){
    _iVar = test;
    NSLog(@"[%@]", test);
}];

但是,要解决此问题(使用ARC):

However, to fix this (using ARC):

__weak IVarClass *iVar = _iVar;
[object testWithBlock:^(NSString *test){
    iVar = test;
    NSLog(@"[%@]", test);
}];

您还可以执行以下操作:

You can also do something like this:

__weak ClassOfSelf _self = self;
[object testWithBlock:^(NSString *test){
    _self->_iVar = test;
    NSLog(@"[%@]", test);
}];

请注意,许多人不喜欢上面的内容,因为他们认为上面的内容很脆弱,但这是访问变量的有效方法. Update -现在,如果您尝试使用'->'直接访问变量,则当前的编译器会发出警告.由于这个原因(以及安全性原因),最好为要访问的变量创建一个属性.因此,您可以使用_self.iVar = test;代替_self->_iVar = test;.

Note that many people don't like the above because they consider it fragile, but it is a valid way of accessing variables. Update - The current compiler now warns if you try to directly access a variable using '->'. For this reason (as well as reasons of safety) it's best to create a property for the variables you want to access. So instead of _self->_iVar = test; you would use: _self.iVar = test;.

更新(更多信息)

通常,最好将接收该块的方法视为负责确定是否需要复制该块而不是调用方的方法.这是因为接收方法可能是唯一知道该块需要保留多长时间或是否需要复制的方法.您(作为程序员)显然会在编写调用时知道此信息,但是如果您在思想上将调用者和接收者放在单独的对象上,则调用者将为接收者提供该块并完成该块.因此,它在块消失之后就不需要知道如何处理.另一方面,调用者很有可能已经复制了该块(也许它已经存储了该块,现在正将其传递给另一种方法),但是接收者(也打算存储该块)仍应该复制该块(即使该块已被复制).接收者无法知道该块已被复制,接收到的某些块可能已被复制,而其他块可能尚未被复制.因此,接收方应始终复制打算保留的块?有道理?这本质上是良好的面向对象设计实践.基本上,掌握信息的人都有责任对其进行处理.

Generally, it's best to consider the method that receives the block as responsible for determining whether the block needs to be copied rather than the caller. This is because the receiving method can be the only one that knows just how long the block needs to be kept alive or if it needs to be copied. You (as the programmer) will obviously know this information when you write the call, but if you mentally consider the caller and receiver at separate objects, the caller gives the receiver the block and is done with it. Therefore, it shouldn't need to know what is done with the block after it's gone. On the flip side, it's quite possible that the caller may have already copied the block (maybe it stored the block and is now handing it off to another method) but the receiver (who also intends on storing the block) should still copy the block (even though the block as already been copied). The receiver can't know that the block has already been copied, and some blocks it receives may be copied while others may not be. Therefore the receiver should always copy a block it intends on keeping around? Make sense? This essentially is good Object Oriented Design Practices. Basically, whoever has the information is responsible for handling it.

在Apple的GCD(中央分配器)中广泛使用了块,以轻松地启用多线程.通常,在GCD上分发块时,无需复制块.奇怪的是,这有点违反直觉(如果您考虑一下的话),因为如果异步调度一个块,通常创建该块的方法将在该块执行之前返回,这通常意味着该块将过期,因为堆栈对象.我认为GCD不会将块复制到堆栈中(我在某个地方读取了该块,但一直无法找到它),相反,我认为通过放置在另一个线程上可以延长该线程的寿命.

Blocks are used extensively in Apple's GCD (Grand Central Dispatch) to easily enable multi-threading. In general, you don't need to copy a block when you dispatch it on GCD. Oddly, this is slightly counter-intuitive (if you think about it) because if you dispatch a block asynchronously, often the method the block was created in will return before the block has executed, which generally would mean the block would expire since it is a stack object. I don't think that GCD copies the block to the stack (I read that somewhere but have been unable to find it again), instead I think the life of the thread is extended by being put on another thread.

Mike Ash在块,GCD和ARC上有大量文章,您可能会发现它们有用:

Mike Ash has extensive articles on blocks, GCD and ARC which you may find useful:

  • Mike Ash: Practical Blocks
  • Mike Ash: Objective-C Blocks vs C++0x Lambdas: Fight!
  • Mike Ash: Q&A - More info on Blocks
  • Mike Ash: Automatic Reference Counting He talks about blocks & ARC in this article.
  • Mike Ash: GCD Is Not Blocks, Blocks Are Not GCD
  • Apple Docs - Introducing Block & GCD

这篇关于在Objective-C中传递块的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆