一、block的内存管理
- 当block在栈上时,并不会对
__block
变量产生强引用。 - 当block被copy到堆时,会调用block内部的copy函数,copy函数内部会调用
_Block_object_assign
函数,_Block_object_assign
函数会对__block
变量形成强引用(retain)
示例代码:
1 | __block int age = 10; |
默认情况下,变量age和定义的block都是在栈上的,由于是ARC环境,定义的block被变量block强引用,block自动从栈copy到堆。当block被copy到堆时,会把block内部用到的变量也从栈copy到堆。
注意:如果多个block内部捕获的是同一个变量,该变量copy到堆中时只会copy一份,内部操作的是引用计数。
- 当block从堆中移除时,会调用block内部的dispose函数,dispose函数内部会调用
_Block_object_dispose
函数,_Block_object_dispose
函数会自动释放引用的__block
变量(release)。
1.1. __block
变量、对象类型的auto变量
当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*/);
1.2. 被__block
修饰的对象类型
- 当
__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)。
示例代码:
1 | @interface DBPerson : NSObject |
1 | struct __Block_byref_person_0 { |
上面代码中的person对象使用__block
修饰,并且person对象是在block被释放后才开始释放的。因此,block内部对__Block_byref_weakPerson_0
类型的person进行的是强引用,而且person转成的结构体对象__Block_byref_weakPerson_0
中的person也是强引用。
只要使用
__block
修饰对象,转成的结构体对象被copy到堆时都会生成一个copy和dispose函数,用来进行内存管理。
如果把上面的person对象使用__weak
修饰会怎么样?
1 | DBPerson *person = [[DBPerson alloc] init]; |
1 | struct __Block_byref_weakPerson_0 { |
person被提前释放了,说明使用__weak
修饰后,person转成的结构体对象__Block_byref_weakPerson_0
中的person是弱引用,但是impl中的__Block_byref_weakPerson_0
类型的person是强引用。
从上面的案例可以看出,__weak
仅针对变量转为结构体后,对结构体内部的变量进行弱引用。如果不使用__weak
,在ARC环境下就是强引用。在MRC环境下,不管是否使用__weak
,最终都是弱引用,如果想要强引用,就不能加上__block
,相当于block直接捕获外部对象。
1.3. __block
的__forwarding
指针
经过前面的了解我们知道,__block
修饰的变量会被封装成一个结构体对象,而且操作这个变量时,访问的是__forwarding
中的变量,那么这个结构体中的__forwarding
指针为什么要指向自己呢?主要是防止block被copy到堆后,操作的还是栈上的block,__forwarding
能够保证操作的一定是堆上的数据。
1 | // age被包装成对象 |
二、block的循环引用
示例代码:
1 | typedef void (^DBBlock)(void); |
疑问:为什么DBPerson类的dealloc
方法没有执行?
因为person类中的成员变量block和self形成了循环引用。在main方法中创建了person对象(person对象引用计数是1),在test方法中,block内部捕获了局部变量self(person对象引用计数是2),而self又持有成员变量block。当person出了作用域后就会被释放一次(此时person对象引用计数是1),由于block内部强引用了self(self就是person对象),因此不会执行dealloc
。
2.1. 解决循环引用
用__weak
、__unsafe_unretained
可以解决block的循环引用问题。
如上面的示例代码可以修改如下:
1 | - (void)test { |
使用__weak
修饰self后,person对象被完全正常释放了,因为block中的self是弱引用。
__unsafe_unretained
字面意思是不安全、不会产生强引用。和__weak
的使用效果是一样的,唯一区别:使用__weak
修饰的对象被block引用后,当对象释放后,指向对象的**指针会指向nil
。使用__unsafe_unretained
修饰的对象最终被释放后,指向对象的指针不会指向nil
**,依旧指向原来对象的内存(已经被释放),此时的指针也叫作野指针。
用__block
也可以解决循环引用问题(必须要调用block)。
1 | - (void)test { |
上面代码如果不调用block,三个对象(__block
生成的结构体对象、person对象、block结构体对象)就会形成相互引用。
调用block后就会打破这种强引用关系:让__block
结构体对象弱引用person对象,在block执行体中加上weakSelf = nil
就可以断掉__block
变量对person对象的强引用关系。
如果是MRC环境,不支持
__weak
,使用__unsafe_unretained
解决循环引用。但是使用__block
也能够解决循环引用,因为在MRC环境,__block
生成的结构体对象内部不会对原来的对象产生强引用。
思考:如果在block内部使用强指针引用block外部的弱指针,会出现什么情况?
1 | - (void)test { |
上面的代码打印后显示person对象被释放,也就是没有产生循环引用。而且在block内部不能使用weakSelf
访问成员变量_name
,因为weakSelf
指向的对象可能会被提前释放。如果使用__strong
修饰一个弱引用指针,weakSelf
就不会立即被释放。
面试题1:block的原理是怎样的?本质是什么?
解答:封装了函数调用以及调用环境的OC对象。
面试题2:__block
的作用是什么?有什么使用注意点?
解答: __block
主要是为了在ARC环境下能够在block内部修改外部的变量。使用时需要注意循环引用。
面试题3:block的属性修饰词为什么是copy?使用block有哪些使用注意?
解答:block一旦没有进行copy操作,就不会在堆上(堆上的目的:自由控制block的生命周期)。使用注意:循环引用问题。
面试题4:block在修改NSMutableArray,需不需要添加__block
?
解答:不需要。因为操作的是array指针,不是对象。