参考文章:深入研究Block捕获外部变量和__block实现原理
做一些简单的总结说明:
(1)对于四种非对象变量:
- 自动变量(局部变量)
- 静态变量
- 静态全局变量
- 全局变量
首先: Block会捕获哪些变量?如果Block外面还有很多自动变量
,静态变量
,等等,这些变量在Block里面并不会被使用到
。那么这些变量并不会被Block捕获进来,也就是说并不会在构造函数里面传入它们的值。Block捕获外部变量仅仅只捕获Block闭包里面会用到的值
,其他用不到的值,它并不会去捕获。
其次:全局变量
和静态全局变量
可以在Block内值被修改是为什么呢?全局变量
和静态全局变量
在执行Block语法的时候,它们被Block捕获进去,这一点很好理解,因为是全局
的,作用域很广,所以Block捕获了它们进去之后,在Block里面进行++操作,Block结束之后,它们的值依旧可以得以保存下来。
然后:对于静态变量
Block是如何捕获的呢?静态变量传递给Block是内存地址值
,所以能在Block里面直接改变值。在执行Block语法的时候,Block语法表达式所使用的静态变量的地址
是被保存进了Block的结构体实例中,也就是Block自身中。所以能够在Block 内部修改静态变量的值。
最后:为什么自动变量
无法在Block内部修改值呢?类似静态变量,自动变量也是在执行Block语法的时候,被block捕获成为Block的结构体实例中,但是Block仅仅捕获了val的值,并没有捕获val的内存地址,所以在Block内部是无法修改自动变量的值。OC可能是基于这一点,在编译的层面就防止开发者可能犯的错误,因为自动变量没法在Block中改变外部变量的值,所以编译过程中就报编译错误。错误:Variable is not assignable(missing __block type specifier)
后面会说明。
总结一下在Block中改变变量值有2种方式,一是传递内存地址指针到Block中,二是改变存储区方式(__block)。
1 传递内存地址
对于对象变量,在被Block捕获后,在Block的结构体实例变量会增加一个指针,所以传递的是指针,所以成功改变了变量的值。
2 __block 改变存储方式。
_block修饰自动变量后,_block的变量也被转化成了一个结构体
:__Block_byref_i_0,这个结构体有5个成员变量。
struct __Block_byref_i_0 {
void *__isa; 指针
__Block_byref_i_0 *__forwarding; 指向自身类型的__forwarding指针
int __flags; 标记flag
int __size;大小
int i; 变量值
};
MRC
环境下,只有copy
,_block才会被复制到堆上,否则,_block一直都在栈上,block也只是 _NSStackBlock,这个时候_forwarding指针就只指向自己了。
ARC
环境下,一旦Block赋值就会触发copy,_block就会copy到堆上,Block也是_NSMallocBlock。ARC
环境下也是存在_NSStackBlock的时候,这种情况下,_block就在栈上
在ARC环境下,Block也是存在__NSStackBlock的时候的,平时见到最多的是_NSConcreteMallocBlock,是因为我们会对Block有赋值操作,所以ARC下,block 类型通过=进行传递时,会导致调用objc_retainBlock->_Block_copy->_Block_copy_internal方法链。并导致 __NSStackBlock__ 类型的 block 转换为 __NSMallocBlock__ 类型
_forwarding
指针初始化传递的是自己的地址,在执行Block的时候,堆上的Block会持有对象。 当我们把Block复制到堆上,堆上的Block也会持有_block.当Block释放的时候,_block没有被任何对象引用,也会被释放销毁。堆上的_forwarding
指针也指向自己,只不过一个指针是_NSConcreteStackBlock,一个是_NSConcreteMallocBlock,两份_block ,栈上的_forwarding
指针指向堆区的block,堆区的_forwarding
指针指向原来的自己所以这样不管_block怎么复制到堆上,还是在栈上,都可以通过(i->__forwarding->i)来访问到变量值。
Block 分类
OC中,一般Block就分为以下3种,_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock。
先来说明一下3者的区别。
_NSConcreteStackBlock:
只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。
StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了。_NSConcreteMallocBlock:
有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制_NSConcreteGlobalBlock:
没有用到外界变量或只用到全局变量、静态变量的block为
_NSConcreteGlobalBlock,生命周期从创建到应用程序结束。
没有用到外部变量肯定是_NSConcreteGlobalBlock,这点很好理解。不过只用到全局变量、静态变量的block也是_NSConcreteGlobalBlock。
weakSelf StrongSelf 的使用
解决循环应用的问题一定要分析清楚哪里出现了循环引用,只需要把其中一环加上weakSelf这类似的宏,就可以解决循环引用。_weak的实现原理,在原对象释放之后,_weak对象就会变成null,防止野指针。所以就输出了null了。
那么我们怎么才能在weakSelf之后,block里面还能继续使用weakSelf之后的对象呢?
究其根本原因就是weakSelf之后,无法控制什么时候会被释放,为了保证在block内不会被释放,需要添加_strong。
在block里面使用的_strong修饰的weakSelf是为了在函数生命周期中防止self提前释放。strongSelf是一个自动变量当block执行完毕就会释放自动变量strongSelf不会对self进行一直进行强引用。
__weak typeof(student) weakSelf = student;
student.study = ^{
__strong typeof(student) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"my name is = %@",strongSelf.name);
});
};
_block 与_weak 的区别
1._block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
2._weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
3._block对象可以在block中被重新赋值,_weak不可以。