一、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下,