一、block内部修改外部变量的值
1、通过static修饰的变量
- static修饰的变量, 在block内可以修改变量的值

- 原因是因为, 在底层block捕获的是
age的地址, 而不是age存储的数据

2、全局变量
- 全局变量可以直接在block中修改值

- block不会捕获全局变量, 而是直接使用, 所以可以直接改值

3、__block修饰的auto变量

问: 为什么__block修饰的变量, 可以在block内修改值?
二、__block
-
__block可以解决block内部无法修改auto变量值的问题 -
__block不能修饰全局变量和静态变量(static), 只能修饰auto变量 -
查看上面代码在底层的结构

-
可以看到在
main函数中,__block int age = 20在底层是红框中的代码,age被包装成了__Block_byref_age_0类型的结构体 -
查看
block的结构, 可以看到block中捕获到的是__Block_byref_age_0类型的指针age, 如下图

__Block_byref_age_0中有五个成员变量, 这五个变量在main函数中创建age时传入参数void *__isa;: 0__Block_byref_age_0 *__forwarding;:age的地址(自身地址)int __flags;: 0int __size;:__Block_byref_age_0占用内存大小int age;:__block修饰的变量age的值

- 当block调用时,会通过
age->__forwarding->age找到__Block_byref_age_0中的成员变量, 并修改值

- 这就是为什么
__block会修改block内部auto变量的值
我们在main函数中使用的age, 究竟是被包装的__Block_byref_age_0还是__Block_byref_age_0里面的成员变量age?
- 通过查
block底层的代码可以看到main函数中定义的变量age已经被包装成了一个__Block_byref_age_0对象, 而block在底层是__main_block_impl_0类型 - 所以我们可以通过类型转换来查看block

- 通过控制台打印
age的地址

三、内存管理
1、基本数据类型的auto变量
- 当栈上
block复制到堆上时, 会直接将捕获的基本数据类型变量复制到堆中

- 底层结构如下

2、对象类型的auto变量
- 当block复制到堆上时, 如果捕获的auto变量是对象类型, 那么就会有两种情况
__strong修饰的变量

- 底层结构如下

- 当变量被强引用修饰时, block复制到堆上的过程中会调用
copy函数,copy函数内部会调用里面的_Block_object_assign函数, 对被捕获的对象变量进行强引用

- 当block从堆上移除时, 会调用block中的
dispose函数,dispose函数内部会调用里面的_Block_object_dispose函数,将对被捕获对象变量的强引用断开

__weak修饰的变量

- 底层结构如下

- 当对象变量被
__weak修饰时,block从栈中复制到堆中, 依然会调用copy函数,copy函数内部会调用里面的_Block_object_assign函数 - 只不过不会再对被
__weak修饰的变量进行强引用
3、__block修饰的auto变量

- 底层结构如下

__block修饰的变量在底层会被包装成一个__Block_byref_age_0对象,block会捕获__Block_byref_age_0对象- 当
block从栈上复制到堆上时, 就会调用__main_block_desc_0中的copy函数, 对__Block_byref_age_0对象进行强引用 - 当
block从堆上移除时, 就会调用__main_block_desc_0中的dispose函数, 断开对__Block_byref_age_0对象的强引用
总结:
block在栈上时, 并不会对__block变量产生强引用
当block被copy到堆时
1.会调用block内部的copy函数
2.copy函数内部会调用_Block_object_assign函数
3._Block_object_assign函数会对__block变量形成强引用


4、__block的__forwarding指针

- 在
block执行时,会先通过block->age拿到age对象, 然后通过age->__forwarding->age对象拿到age变量

- 当block在栈上时, 通过
__forwarding指针拿到的是栈中的__block结构体

- 当block在堆上时, 通过
__forwarding指针拿到的是堆中的__block结构体

5、对象类型的auto变量、__block变量总结
-
当
block在栈上时,对它们都不会产生强引用 -
当
block拷贝到堆上时,都会通过copy函数来处理它们__block变量(假设变量名叫做a)_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); -
对象类型的
auto变量(假设变量名叫做p)_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/); -
当
block从堆上移除时,都会通过dispose函数来释放它们__block变量(假设变量名叫做a)_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/); -
对象类型的
auto变量(假设变量名叫做p)_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
四、被__block修饰的对象类型
- 创建
Person类, 添加age属性, 并重写dealloc方法

- 使用
__block修饰Person类型的变量

- 查看底层结构如下

- 在
main函数中, 可以找到创建__block对象的代码, 可以看到传入了两个函数地址__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131


- 已知,
block捕获__block变量时, 会使用强引用 - 而在
__block修饰的变量会包装在__Block_byref_person_0中 - 所以,
block的结构如下

-
block中包含struct __Block_byref_person_0 *person;,struct __Block_byref_person_0中包含person对象 -
在
block成员变量__main_block_desc_0结构体中的copy和dispose是用来处理person到struct __Block_byref_person_0之间连线的


- 而
__block对象中的copy和dispose, 是用来处理struct __Block_byref_person_0中__strong person到[[Person alloc] alloc]对象之间连线的


block对象在复制到堆中时,__Block_byref_person_0中的__Block_byref_id_object_copy函数指针调用, 最终调用下面的方法, 用来处理是否对person对象进行强引用
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
- 当block从堆中移除时,
__Block_byref_person_0中的__Block_byref_id_object_dispose函数指针调用, 最终调用下面的方法, 用来断开对person对象的强引用
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
- 在ARC中,
block复制到堆中时是否会对被包装的auto对象变量进行强引用, 也要看__block修饰的是__strong类型还是__weak类型
1、__block修饰__strong类型的对象类型变量
__block修饰__strong类型的对象类型变量, 会对对象类型变量进行强引用



2、__block修饰__weak类型的对象类型变量
__block修饰__weak类型的对象类型变量, 会对对象类型变量进行弱引用



3、MRC环境下, __block修饰__strong类型的对象类型变量
- 修改为MRC环境

- 可以发现, MRC下,
__block修饰的__strong类型的对象类型变量, 在block复制到堆上时,不会进行retain处理 person离开作用域会被释放

总结:
当__block变量在栈上时,不会对指向的对象产生强引用
当__block变量被copy到堆时
会调用__block变量内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)
如果__block变量从堆上移除
会调用__block变量内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放指向的对象(release)\
五、循环引用
- 新建命令行项目, 添加
Person类, 添加block属性和age属性, 如下图

- 当
person的block中使用了person对象本身, 那么就会形成循环引用, 代码如下

- 此时内存中的结构如下

- 当
main函数执行完, 局部变量Person *person会被释放, 此时内存中会存留person和block, 这就造成了block的循环引用问题

- 想要解决这个问题, 只需要打断两条强引用中的一条即可, 而我们需要
person的block属性存在, 所以只需要打断block对person的强引用
1、ARC下解决block循环引用问题
- 使用
__weak和__unsafe_unretained都可以使block对person只进行弱引用__weak: 当person被释放时,block中的__weak person会指向nil__unsafe_unretained: 当person被释放时,block中的__unsafe_unretained person不会指向nil, 造成野指针
- 以__weak为例, 代码如下

- 此时的内存结构如下,
block中的__weak person对person只是弱引用

-
当
main函数执行完,person就会被释放 -
除了
__weak和__unsafe_unretained之外, 还可以使用__block来解决循环引用问题 -
现有如下代码

- 此时内存中的结构如下

-
如果想使用
__block解决block的循环引用问题, 只需要打断__block变量到对象这根线的强引用关系 -
此时代码如下, block必须被调用

- 内存结构如下, 因为没有了
__block变量到对象的强引用, 所以对象可以被释放

2、MRC下解决block循环引用问题

- 可以使用
__unsafe_unretained关键字修饰对象类型的auto变量, 来解决强引用问题

- 还可以使用
__block来修饰对象类型的auto变量, 此时已然可以解决循环引用问题

- 这主要是因为
- 在ARC下,
__block会将修饰的对象类型的auto变量进行强引用 - 在MRC下,
__block不会将修饰的对象类型的auto变量进行强引用
- 在ARC下,