目录
1. block的类型
2. block的copy
3. block对象类型的auto变量的copy分析
4. __block的作用及原理
block的类型
- block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
- NSGlobalBlock ( _NSConcreteGlobalBlock ) 全局block
- NSStackBlock ( _NSConcreteStackBlock )栈block
- NSMallocBlock ( _NSConcreteMallocBlock )堆block
三种block的内存分布如下:
不同类型的block区别和联系:
- 每一种类型的block调用copy后的结果如下所示
block的copy
在实际开发场景中,我们使用block经常都会访问auto变量,所以在MRC的环境下我们要用到copy关键字,将栈block转换为堆block,避免栈block超出作用域被释放;
建议写法:
@property (copy, nonatomic) void (^block)(void);
ARC环境下,因为编译器内部会根据情况自动将栈上的block复制到堆上,copy和strong效果一样, 建议写法:
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
ARC环境下,编译器内部会根据情况自动将栈上的block复制到堆上的几种情况:
- block作为函数返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
block对象类型的auto变量的copy分析
- 先看看栈block对对象类型的auto变量的copy情况: 写一个person类,添加dealloc方法,当person释放时打印"Person - dealloc"
设置开启MRC环境:
看如下代码:
- 定义了一个block,引用了一个peron的auto变量,之前也说到引用auto变量的block,如果没有copy,都是栈block。
MRC环境不会自动copy,所以下面的TestBlock是栈block
可以发现 block还没有释放,person就被释放了,说明block并没有强引用person(person默认是__strong类型)。
- 把block进行copy操作,我们发现person并没有被释放,说明block强引用了person
我们再设置ARC模式下: 将person改为弱引用
发现person被释放了,说明block内部是弱引用了person
为了验证这个问题,我们转为C++看看内部实现:
可以看出来当person是 __weak 修饰则bolck内部的person指针修饰符是__weak
当person是默认修饰符(__strong)修饰时bolck内部的person修饰符是__strong
- 当block被拷贝到堆上:
- 会调用block内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
- block从堆上移除:
- 会调用block内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放引用的auto变量(release)
总结:
- 栈block不会对auto变量产生强引用
- 当栈block被copy到堆上,会根据变量的修饰符决定block对auto变量的引用关系,__weak则为弱引用,__strong则为强引用。
__block的作用及原理
- 我们使用block时,有时候需要在block改变auto变量,但是通过前面熟悉的block的结构,对于auto变量,block内部会生成一个与其对应的变量,实际上并不能直接拿到外面的变量赋值,所以我们需要在变量前面加上__block,则可以进行修改啦。
那么__block里面到底做了什么呢,先看看下面的示例代码:
转换为C++ 主要部分如下:
可以发现被__block修饰后,里面会多生成一个__Block_byref_age_0的结构体,里面包含主要信息是一个指向自身的forwarding指针和变量的值,图示如下:
总结一下:
- __block可以用于解决block内部无法修改auto变量值的问题
- __block不能修饰全局变量、静态变量(static)
- 编译器会将__block变量包装成一个对象,内部是一个与之对应的结构体,包含变量值和指向自身的forwarding指针
- forwarding指针的作用:
主要是为了保证栈block被拷贝到堆上时,栈block和堆block中的变量能同步。
1.当栈上的block被拷贝到堆上时,栈block的forwoarding指针会指向堆block
2.从上面代码也能看出来(截图如下),访问__block变量是通过forwoarding去访问的,所以这时候无论是访问栈block还是堆block,最终取出的变量都是堆block中的变量。