这会导致任何保留环路吗?使用它是否安全?
__block void (^myBlock)(int) = [^void (int i)
{
if (i == 0)
return;
NSLog(@"%d", i);
myBlock(i - 1);
} copy];
myBlock(10);
myBlock = nil;
这会导致任何保留环路吗?使用它是否安全?
__block void (^myBlock)(int) = [^void (int i)
{
if (i == 0)
return;
NSLog(@"%d", i);
myBlock(i - 1);
} copy];
myBlock(10);
myBlock = nil;
您的代码确实包含保留循环,但您可以在递归结束时通过在递归基本情况(i == 0
)中将myBlock
设置为nil来打破保留循环。
证明这一点的最佳方法是尝试在“分配”工具下运行它,关闭“停止时丢弃未记录的数据”,打开“记录引用计数”,并关闭“仅跟踪活动分配”。
我使用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;
}
然后我在分配仪器下运行它,并使用我上面描述的设置。然后我在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
),将“Console”更改为“Objects List”,然后将该地址粘贴到搜索框中。Instruments 仅显示分配给该块的部分:
Live 列下的点表示程序退出时该块仍处于分配状态。它被泄漏了。我点击地址旁边的箭头以查看块分配的完整历史记录:
这个分配只发生过一件事:它被分配了。
接下来,我取消了 if (i == 0)
语句中的 myBlock = nil
行的注释。然后我再次在性能分析器下运行了它。系统会出于安全考虑随机化内存地址,因此我清空了搜索栏,然后再次检查 Console 以获取此次运行中块的地址。这一次它是 0x7fc7a1424700
。我再次切换到“Objects List”视图,并粘贴新地址 0x7fc7a1424700
。这是我看到的:
这一次 Live 列下没有点,这意味着该块在程序退出时已经被释放。然后我点击地址旁边的箭头以查看完整的历史记录:
这一次,该块被分配、释放和释放。
__block
变量被限定为__strong
,并保留其所引用的对象。 - rob mayoff有一个简单的解决方案可以避免循环和可能需要过早复制的情况:
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);
如果你想将它扩展为相互递归,那么这将变得繁琐,但我想不到一开始就要这样做的好理由(用类可能更清晰明了)。
__block
对象变量被块(block)所保留。这样,该块将保留自身。你可以通过将myBlock
声明为__block
和__weak
来避免这种情况。__block
对象变量不会被保留,因此你不应该有问题。只需记得在最后释放myBlock
即可。我希望有一个不会出现警告的解决方案,在这个帖子https://dev59.com/Y2445IYBdhLWcg3wgqnT#17235341中Tammo Freese提供了最好的解决方案:
__block void (__weak ^blockSelf)(void);
void (^block)(void) = [^{
// Use blockSelf here
} copy];
blockSelf = block;
// Use block here
myBlock(10)
后释放。我已经将这个添加到答案中了。 - JoemyBlock
,并且尝试使用Block_release
删除它将导致EXEC_BAD_ACCESS错误,当ARC尝试释放它时,或者更糟糕的是,释放错误的对象。 - Richard J. Ross III__block
在 ARC 下确实会复制其内容,不像你所说的,而且在 ARC 下不需要释放块。 - Richard J. Ross III这里是一个现代化的解决方案:
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);