Block的引用循环问题 (ARC & non-ARC)

本文深入解析Objective-C中Block的全局、栈和堆分配方式,阐述了Block如何影响作用域外变量的访问及修改,并讨论了Block在不同作用域中的生命周期管理,特别强调了循环引用问题及其解决策略。

NSConcreteGlobalBlock:全局类型Block,在编译器就已经确定,直接放在代码段TEXT上。直接在NSLog中打印的类型为NSGlobalBlock_。 NSConcreteStackBlock:位于栈上分配的Block,即_NSStackBlock。 NSConcreteMallocBlock:位于堆上分配的Block,即_NSMallocBlock

void addBlock(NSMutableArray *array)
{
[array addObject:^{
printf("global block\n");
}];
}

void example()
{
NSMutableArray *array = [NSMutableArray array];
addBlock(array);
void (^block)() = [array objectAtIndex:0];
block();
}

为什么addBlock中添加到array中的Block属于全局Block呢?因为它不需要运行时(Runtime)任何的状态来改变行为,不需要放在堆上或者栈上,直接编译后在代码段中即可,就像个c函数一样。这种类型的Block在ARC和non-ARC情况下没有差别。

void example()
{
int d = 5;
void (^block)() = ^() {
printf("%d\n", d);
};
block();
}

这个Block访问了作用域外的变量d,在实现上就是这个block会多一个成员变量对应这个d,在赋值block时会将方法exmpale中的d变量值复制到成员变量中,从而实现访问。

void example()
{
int d = 5;
void (^block)() = ^() {
d++;
printf("%d\n", d);
};
block();
printf("%d\n", d);
}
由于局部变量d和这个block的实现不在同一作用域,仅仅在调用过程中
用到了值传递,所以不能直接修改,而需要加一个标识符__block int d = 5;那么block就可以实现对这个局部变量的修改了。如果是这种block标识的变量,在Block实现中不再是简单的一个成员变量,而是对应一个新的结构体表示这个block变量。
block的本质是引入了一个新的Block_byref{$var_name}{$index}结构体,被block关键字修饰的变量就被放到这个结构体中。另外,block结构体通过引入Block_byref{$var_name}{$index}指针类型的成员,得以间接访问到Block的外部变量。这样对Block外的变量访问从值传递转变为引用,从而有了修改内容的能力。

正常我们使用Block是在栈上生成的,离开了栈作用域便释放了,如果copy一个Block,那么会将这个Block copy到堆上分配,这样就不再受栈的限制,可以随意使用啦。

typedef void (^TestBlock)();
TestBlock getBlock()
{
char e = 'E';
void (^returnedBlock)() = ^{
printf("%c\n", e);
};
return returnedBlock;
}
void example()
{
TestBlock block = getBlock();
block();
}

函数getBlock中声明并赋值的returnedBlock,一开始是在栈上分配的,属于NSStackBlock,如果是non-ARC情况下return这个NSStackBlock,那么其实已经被销毁了,在函数中example()使用时就会crash。如果是ARC情况下,getBlock返回的block会自动copy到堆上,那么block的类型就是NSMallocBlock,可以在example()中继续使用。要在Non-ARC情况下正常运行,那么就应该修改为:

TestBlock getBlock()
{
char e = 'E';
void (^returnedBlock)() = ^{
printf("%c\n", e);
};
return [[returnedBlock copy] autorelease];
}

Block中的循环引用问题
扯了这么多,回到Block的循环引用问题,由于我们很多行为会导致Block的copy,而当Block被copy时,会对block中用到的对象产生强引用(ARC下)或者引用计数加一(non-ARC下)。
如果遇到这种情况:

@property(nonatomic, readwrite, copy) completionBlock completionBlock;

//========================================
self.completionBlock = ^ {
if (self.success)
{
self.success(self.responseData);
}
}
};

对象有一个Block属性,然而这个Block属性中又引用了对象的其他成员变量那么就会对这个变量本身产生强应用,那么变量本身和他自己的Block属性就形成了循环引用。在ARC下需要修改成这样:

@property(nonatomic, readwrite, copy) completionBlock completionBlock;
//========================================

__weak typeof(self) weakSelf = self;
self.completionBlock = ^ {
if (weakSelf.success)
{
weakSelf.success(weakSelf.responseData);
}
};

也就是生成一个对自身对象的弱引用,如果是倒霉催的项目还需要支持iOS4.3,就用unsafe_unretained替代weak。如果是non-ARC环境下就将weak替换为block即可。non-ARC情况下,block变量的含义是在Block中引入一个新的结构体成员变量指向这个block变量,那么__block typeof(self) weakSelf = self;
就表示Block别再对self对象retain啦,这就打破了循环引用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值