这会导致任何类型的保留周期吗?使用安全吗?
__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;
您的代码确实包含一个保留周期,但您可以通过在递归基本案例 ( )中设置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 列下没有点,这意味着程序退出时该块已被释放。然后我点击地址旁边的箭头查看完整的历史记录:
这一次,块被分配、释放和释放。
有一个简单的解决方案可以避免循环和过早复制的潜在需要:
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);
如果您想扩展它以进行相互递归,这将变得乏味,但我想不出一个好的理由首先这样做(一个类不会更清楚吗?)。
如果您使用 ARC,则有一个保留循环,因为__block
对象变量由块保留。所以块保留自己。您可以通过将两者声明myBlock
为__block
and来避免它__weak
。
如果您使用的是 MRC,__block
则不会保留对象变量,您应该没有问题。只记得myBlock
最后释放。
不,这不会导致保留周期。__block
关键字告诉块不要复制,这myBlock
会在分配之前发生,导致应用程序崩溃。如果这不是 ARC,那么您唯一需要做的就是myBlock
在调用后释放myBlock(10)
。
我想要一个没有警告的解决方案,在这个线程中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
他的解释很有道理。
这是该问题的现代解决方案:
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);