Block
block的本质 -- -- OC对象
block最终都是继承自NSBlock类型,而NSBlock继承于NSObjcet。那么block其中的isa指针其实是来自NSObject中的。这也更加印证了block的本质其实就是OC对象
block的捕获机制
局部变量:
会被block捕获到内部
auto类型的是值传递,内部不能修改值
static是指针传递,可以修改值。
全局变量:
不会被捕获到block内部
可以修改值
Q:为什么局部变量需要捕获?
考虑作用域的问题,需要跨函数访问,就需要捕获
<style>
td
{
text-align:center;
}
</style>
<table border="1" bgcolor="#778899" class="td">
<tr>
<th colspan="2">变量类型</th>
<th>捕获到block内部</th>
<th>访问方式</th>
</tr>
<tr>
<td rowspan="2">局部变量</td>
<td>auto</td>
<td>√️</td>
<td>值传递</td>
</tr>
<tr>
<td>static</td>
<td>√</td>
<td>地址传递</td>
</tr>
<tr>
<td colspan="2">全局变量</td>
<td>×</td>
<td>直接访问</td>
</tr>
</table>
(html代码转义失败了,直接看截图吧😅)
block有哪几种类型
- _NSConcreteStackBlock:
只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。
StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了。 - _NSConcreteMallocBlock:
有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制 - _NSConcreteGlobalBlock:
没有用到外界变量或只用到全局变量、静态变量的block为_NSConcreteGlobalBlock,生命周期从创建到应用程序结束
ARC环境下,一旦Block赋值就会触发copy,
__block
就会copy到堆上,Block也是__NSMallocBlock
。ARC环境下也是存在__NSStackBlock
的时候,这种情况下,__block
就在栈上。
MRC环境下,只有copy,__block
才会被复制到堆上,否则,__block
一直都在栈上,block也只是__NSStackBlock
,这个时候__forwarding
指针就只指向自己了。
在MRC环境下,
__block
根本不会对指针所指向的对象执行copy操作,而只是把指针进行的复制。
而在ARC环境下,对于声明为__block
的外部对象,在block内部会进行retain,以至于在block环境内能安全的引用外部对象,所以才会产生循环引用的问题!
<pre>
int age = 1;
void (^block1)(void) = ^{
NSLog(@"block1");
};
void (^block2)(void) = ^{
NSLog(@"block2:%d",age);
};
NSLog(@"%@/%@/%@",[block1 class],[block2 class],[^{
NSLog(@"block3:%d",age);
} class]);
</pre>
堆block
1.如果block被拷贝到堆上:
- 会调用block内部的copy函数
- copy函数内部会调用
_Block_object_assign
函数 -
_Block_object_assign
函数会根据auto变量的修饰符 (__strong、__weak、__unsafe_unretained
)做出相应的操作,形成强引用(retain)或者弱引用
2.如果block从堆上移除
- 会调用block内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放引用的auto变量(release)
Q:当block内部访问了对象类型的auto变量时,是否会强引用?
- 如果block在栈空间,不管外部变量是强引用还是弱引用,block都会弱引用访问对象
- 如果block在堆空间,如果外部强引用,block内部也是强引用;如果外部弱引用,block内部也是弱引用
解决堆block的循环引用办法:
- 释放block
- 在block里不要强引用对象
masonry解决的办法是释放block
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
看as_makeConstraints的方法实现会发现设置布局的方法中的block对象并没有被View所引用,
block(constraintMaker)
而是直接在方法内部同步执行,执行完以后block将释放,其中捕捉的外部变量的引用计数也将还原到之前。这个block只是个栈block,栈block有个特性就是它执行完毕之后就出栈,出栈了就会被释放掉,构不成循环引用。
__weak typeof(person) weakPerson = person;
__unsafe_unretained Person *person = [[Person alloc] init];
__block Person *person = [[Person alloc] init];
__weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
__unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
__block:必须把引用对象置为nil,并且要调用该block
weakSelf 是为了block不持有self,避免Retain Circle循环引用。在 Block 内如果需要访问 self 的方法、变量,建议使用 weakSelf。strongSelf的目的是因为一旦进入block执行,假设不允许self在这个执行过程中释放,就需要加入strongSelf。block执行完后这个strongSelf 会自动释放,没有不会存在循环引用问题。如果在 Block 内需要多次 访问 self,则需要使用 strongSelf。
strongSelf实质是一个局部变量(在block这个“函数”里面的局部变量),当block执行完毕就会释放自动变量strongSelf,不会对self进行一直进行强引用。