20

这会导致任何类型的保留周期吗?使用安全吗?

__block void (^myBlock)(int) = [^void (int i)
{
    if (i == 0)
        return;

    NSLog(@"%d", i);
    myBlock(i - 1);
} copy];
myBlock(10);

myBlock = nil;
4

6 回答 6

34

您的代码确实包含一个保留周期,但您可以通过在递归基本案例 ( )中设置myBlock为 nil 在递归结束时打破保留周期。i == 0

证明这一点的最好方法是尝试它,在 Allocations 工具下运行,关闭“Discard unrecorded data on stop”,打开“Record reference counts”,关闭“Only track active allocations”。

我使用 OS X 命令行工具模板创建了一个新的 Xcode 项目。这是整个程序:

#import <Foundation/Foundation.h>

void test() {
    __block void (^myBlock)(int) = [^void (int i){
        if (i == 0) {
//            myBlock = nil;
            return;
        }
        NSLog(@"myBlock=%p %d", myBlock, i);
        myBlock(i - 1);
    } copy];
    myBlock(10);
}

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        test();
    }
    sleep(1);
    return 0;
}

然后我在 Allocations 工具下运行它,使用上面描述的设置。然后我在 Instruments 中将“Statistics”更改为“Console”,查看程序输出:

2012-10-26 12:04:31.391 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 10
2012-10-26 12:04:31.395 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 9
2012-10-26 12:04:31.396 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 8
2012-10-26 12:04:31.397 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 7
2012-10-26 12:04:31.397 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 6
2012-10-26 12:04:31.398 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 5
2012-10-26 12:04:31.398 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 4
2012-10-26 12:04:31.399 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 3
2012-10-26 12:04:31.400 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 2
2012-10-26 12:04:31.401 recursiveBlockTest[71789:303] myBlock=0x7ff142c24700 1
<End of Run>

我复制了块地址(0x7ff142c24700),将“控制台”更改为“对象列表”,并将地址粘贴到搜索框中。Instruments 只向我展示了该块的分配:

块泄露

Live 列下的点表示程序退出时该块仍被分配。它被泄露了。我点击了地址旁边的箭头来查看区块分配的完整历史:

阻止泄露的细节

这种分配只发生过一件事:它被分配了。

接下来我取消注释语句myBlock = nil中的行。if (i == 0)然后我再次在分析器下运行它。为了安全起见,系统会随机分配内存地址,因此我清除了搜索栏,然后再次检查控制台以获取本次运行的块地址。正是0x7fc7a1424700这个时候。我再次切换到“对象列表”视图并粘贴到新地址中0x7fc7a1424700。这是我看到的:

块释放

这次 Live 列下没有点,这意味着程序退出时该块已被释放。然后我点击地址旁边的箭头查看完整的历史记录:

块释放细节

这一次,块被分配、释放和释放。

于 2012-10-26T17:17:43.860 回答
14

有一个简单的解决方案可以避免循环和过早复制的潜在需要:

void (^myBlock)(id,int) = ^(id thisblock, int i) {
    if (i == 0)
      return;

    NSLog(@"%d", i);
    void(^block)(id,int) = thisblock;
    block(thisblock, i - 1);
  };

myBlock(myBlock, 10);

您可以添加一个包装器来获取原始类型签名:

void (^myBlockWrapper)(int) = ^(int i){ return myBlock(myBlock,i); }

myBlockWrapper(10);

如果您想扩展它以进行相互递归,这将变得乏味,但我想不出一个好的理由首先这样做(一个类不会更清楚吗?)。

于 2013-06-03T19:30:24.153 回答
2

如果您使用 ARC,则有一个保留循环,因为__block对象变量由块保留。所以块保留自己。您可以通过将两者声明myBlock__blockand来避免它__weak

如果您使用的是 MRC,__block则不会保留对象变量,您应该没有问题。只记得myBlock最后释放。

于 2012-10-27T19:17:56.517 回答
1

不,这不会导致保留周期。__block关键字告诉块不要复制,这myBlock会在分配之前发生,导致应用程序崩溃。如果这不是 ARC,那么您唯一需要做的就是myBlock在调用后释放myBlock(10)

于 2012-10-26T16:35:10.683 回答
1

我想要一个没有警告的解决方案,在这个线程中https://stackoverflow.com/a/17235341/259521 Tammo Freese 给出了最好的解决方案:

__block void (__weak ^blockSelf)(void);
void (^block)(void) = [^{
        // Use blockSelf here
} copy];
blockSelf = block;
    // Use block here

他的解释很有道理。

于 2014-08-10T15:05:15.057 回答
0

这是该问题的现代解决方案:

void (^myBlock)();
__block __weak typeof(myBlock) weakMyBlock;
weakMyBlock = myBlock = ^void(int i) {
    void (^strongMyBlock)() = weakMyBlock; // prevents the block being delloced after this line. If we were only using it on the first line then we could just use the weakMyBlock.
    if (i == 0)
        return;

    NSLog(@"%d", i);
    strongMyBlock(i - 1);
};
myBlock(10);
于 2016-01-14T01:56:16.877 回答