一、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中捕获到了personauto变量, 并且是一个__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复制到堆中时不会进行强引用