一、block的类型
-
block
有3
种类型,可以通过调用class
方法或者isa
指针查看具体类型,最终都是继承自NSBlock
类型__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
-
这三种类型在内存中分别存储在不同的区域
__NSGlobalBlock__
存在于内存的数据区域(.data区)__NSStackBlock__
存在于内存的栈区__NSMallocBlock__
存在于内存的堆区
三种类型的block有什么区别?
二、block的三种类型
- 首先将程序设置成
MRC
模式(ARC
在后面)
1、__NSGlobalBlock__
- 内部没有使用
auto
类型变量的block
, 就是__NSGlobalBlock__
类型
2、__NSStackBlock__
- 内部使用了
auto
类型变量的block
, 就是__NSStackBlock__
类型
3、__NSMallocBlock__
__NSStackBlock__
类型的block
调用copy
后就是__NSMallocBlock__
类型, 通过copy
, 将block
从栈区复制到了堆区
__NSGlobalBlock__
类型的block
调用copy
后类型不变, 还是__NSGlobalBlock__
类型(还在数据区)
__NSMallocBlock__
类型的block
调用copy
后类型不变, 还是__NSMallocBlock__
类型(不会生成新的block, 原有引用计数+1)
4、block类型总结
三、ARC环境下, block的类型问题
- 将程序设置回ARC环境
- 在ARC环境下, 编译器会根据情况自动将栈上的block复制到堆上, 比如以下情况
1、__NSStackBlock__
类型的block做为函数返回值时, 会将返回的block
复制到堆区
2、将__NSStackBlock__
类型的block赋值给__strong
指针时, 会将block
复制到堆区
- 如果没有
__strong
指针引用__NSStackBlock__
类型的block
, 那么block
的类型依然是__NSStackBlock__
3、block作为Cocoa API中方法名含有usingBlock的方法参数时, block在堆区
- 在
Cocoa API
中会有一些方法, 比如数组的遍历方法
4、block作为GCD API的方法参数时, block在堆区
以上四种方式的block, 都会被复制到堆区
5、__NSGlobalBlock__
类型的block, 不管怎样类型都不会改变, 依然在数据区
四、对象类型的auto变量
- 创建一个类
Person
, 并添加一个age
属性, 重写dealloc
方法
1、MRC情况下, 对象类型的auto变量
- 将程序设置为MRC
- 我们知道, 一个局部的对象变量在离开作用域时会被释放
- 如果栈区的
block
中使用了对象类型的auto对象, 那么auto对象离开作用域还会被释放吗?
- 根据结果可以知道, 处于栈区中的
block
, 如果引用了auto对象, 当auto对象离开作用域时一样会被释放 - 如果将栈区的
block
复制到堆上,又是什么样的结果?
- 根据结果可以知道, 如果将
block
复制到堆上, 此时person
对象没有被释放,说明block
在复制到堆上时对person
进行了一次retain
处理 - 如果我们释放掉堆中的
block
, 可以看到person
也被释放了
- 说明, 当block从堆中释放时, 会对其中引用的aotu对象变量进行一次
release
处理
总结:
栈中的block
不会将对象类型的auto变量
进行retain
处理, 只有在将block
复制到堆上时, 才会将对象类型的auto变量
进行retain
处理(引用计数+1)
当堆中的block
释放时, 会对其中的对象类型的auto变量
进行release
处理(引用计数-1), 如果此时对象类型的auto变量
的引用计数为零, 就会被释放
2、ARC情况下, 对象类型的auto变量
- 将程序设置回ARC环境
- 在ARC下, 局部的对象变量离开作用域时, 一样会被释放
- 此时在block中使用auto变量, 可以看到
person
离开作用域并没有被释放
- 只有当
block
离开作用域被释放时,person
才被释放
- 这主要是因为在ARC下, 一个
__NSStackBlock__
类型的block
被一个__strong
类型的指针引用时, 系统会将block
自动复制到堆区 - 此时的
block
类型是__NSMallocBlock__
, 会对person
进行一次强引用(类似MRC下的retain
操作)
3、查看block底层结构
- 使用终端,
cd
到main.m
文件所在文件夹, 并执行命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
复制代码
-
将生成的
main.cpp
文件拖到项目中打开 -
可以看到,
__main_block_impl_0
中捕获到了person
auto变量, 并且是一个__strong
类型
- 并且, 在
__main_block_desc_0
中也多了两个函数指针copy
和dispose
- 这两个函数指针传入参数是
__main_block_copy_0
和__main_block_dispose_0
两个函数的地址
- 实际上, 当
block
被复制到堆上
时,会调用__main_block_copy_0
函数, 来对捕获的对象类型的auto变量
进行强引用 - 当
block
从堆上移除时, 又会被调用__main_block_dispose_0
函数, 对捕获的对象类型的auto变量
解除强引用 - 而在栈上的
block
, 即使捕获了对象类型的auto变量
,也不会调用__main_block_copy_0
函数和__main_block_dispose_0
函数, 即不会对对象类型的auto变量
进行强引用
4、__weak
- ARC下, 程序提供了
__weak
关键字, 用来修饰对象类型的auto变量
- 当
block
捕获到的对象类型的auto变量
被__weak
修饰时, 即便block
被复制到了堆上,__main_block_copy_0
方法也不会对被捕获的对象类型的auto变量
进行强引用
- 此时
block
的底层结构如下:
- 此时
__main_block_desc_0
中依然有copy
和dispose
, 只不过不在对person
进行强引用
总结:
不论在ARC还是MRC下,栈中的block不会对捕获到的对象类型auto变量
进行强引用(引用计数+1), 只会在copy到堆中时, 会对对象类型auto变量
进行强引用
ARC下, 被__weak修饰的对象类型auto变量
, 在block复制到堆中时不会进行强引用