iOS中的Block详解2(附面试题) - 底层原理总结
block对对象变量的捕获
block一般使用过程中都是对对象变量的捕获,那么对象变量的捕获和基本数据类型变量相同吗?
如下代码中,在block中访问的为对象类型时,对象什么时候销毁?
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
block = ^{
NSLog(@"------block内部%d",person.age);
};
} // 执行完毕,person没有被释放
NSLog(@"--------");
} // person 释放
return 0;
}
大括号执行完毕之后,person
依然不会被释放。上一篇文章提到过,person
为auto
变量,传入的block
的变量同样为person
,即block
有一个强引用引用person
,所以block
不被销毁的话,person
也不会销毁。
查看源码确实如此
![](https://img.haomeiwen.com/i3222009/b76f3d5709d121ae.png)
将上述代码转移到MRC环境下,在MRC环境下即使block还在,person
却被释放了。因为MRC环境下block在栈空间,栈空间对外面的person
不会进行强引用。
//MRC环境下代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
block = ^{
NSLog(@"------block内部%d",person.age);
};
[person release];
} // person被释放
NSLog(@"--------");
}
return 0;
}
block调用copy之后,person不会被释放。
block = [^{
NSLog(@"------block内部%d",person.age);
} copy];
上文中也提到过,只需要对栈空间的block
进行一次copy
操作,将栈空间的block
拷贝到堆中,person
就不会被释放,说明堆空间的block
可能会对person
进行一次copy
操作,以保证person
不会被销毁。堆空间的block
自己销毁之后也会对持有的对象进行release
操作。
也就是说栈空间上的block不会对对象强引用,堆空间的block有能力持有外部调用的对象,即对象进行强引用或去除强引用的操作。
__weak
__weak添加之后,person
在作用域执行完毕之后就被销毁了。
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
__weak Person *waekPerson = person;
block = ^{
NSLog(@"------block内部%d",waekPerson.age);
};
}
NSLog(@"--------");
}
return 0;
}
将代码转化为c++来看一下上述代码之间的差别。__weak修饰变量,需要告知编译器使用ARC环境及版本号,否则会报错,添加说明-fobjc-arc -fobjc-runtime=ios=8.0.0
即xcrun -sdk iponeos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
![](https://img.haomeiwen.com/i3222009/59e2c68c70b04f97.png)
__weak修饰变量,在生成的__main_block_impl_0
中也是使用weak
修饰。
__main_block_copy_0和__main_block_dispose_0
当block中捕获对象类型的变量时,我们发现block结构体__main_block_impl_0
的描述结构体__main_block_desc_0
中多了两个参数copy
和dispose
函数,查看源码:
![](https://img.haomeiwen.com/i3222009/f612343525f83304.png)
copy
和dispose
函数中传入的都是__main_block_impl_0
结构体本身。
copy
本质就是__main_block_copy_0
函数,__main_block_copy_0
函数内部调用_Block_object_assign
函数,_Block_object_assign
中传入的是person对象的地址,person对象,以及8。
dispose
本质就是__main_block_dispose_0
函数,__main_block_dispose_0
函数内部调用_Block_object_dispose
函数,_Block_object_dispose
函数传入的参数是person对象,以及8。
_Block_object_assign函数调用时机及作用
当block进行copy操作的时候就会自动调用__main_block_desc_0
内部的__main_block_copy_0
函数,__main_block_copy_0
函数内部会调用_Block_object_assign
函数。
_Block_object_assign
函数会自动根据__main_block_impl_0
结构体内部的person
是什么类型的指针,对person
对象产生强引用或者弱引用。可以理解为_Block_object_assign
函数内部会对person
进行引用计数器的操作,如果__main_block_impl_0
结构体内person
指针是__strong
类型,则为强引用,引用计数+1,如果__main_block_impl_0
结构体内person
指针是__weak
类型,则为弱引用,引用计数不变。
_Block_object_dispose函数调用时机及作用
当block从堆中移除时就会自动调用__main_block_desc_0
中的__main_block_dispose_0
函数,__main_block_dispose
函数内部会调用_Block_object_dispose
函数。
_Block_object_dispose
会对person
对象做释放操作,类似于release
,也就是断开对person
对象的引用,而person
究竟是否被释放还是取决于person
对象自己的引用计数。
总结
1.一旦block中捕获的变量为对象类型,block
结构体中的__main_block_desc_0
会出现两个参数copy
和dispose
。因为访问的是个对象,block希望拥有这个对象,就需要对对象进行引用,也就是进行内存管理的操作。比如说对对象进行retarn操作,因此一旦block捕获的变量是对象类型就会自动生成copy
和dispose
来对内部引用的对象进行内存管理。
2.当block内部访问了对象类型的auto变量时,如果block是在栈上,block内部不会对person产生强引用。
3.如果block被拷贝到堆上,copy
函数会调用_Block_object_assign
函数,根据auto变量的修饰符(__strong,__weak,__unsafe_unretained)作出相应的操作,形成强引用或者弱引用。
4.如果block从堆中移除,dispose
函数会调用_Block_object_dispose
函数,自动释放引用的auto变量。
问题
1.下列代码person在何时销毁?
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
Person *person = [[Person alloc] init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",person);
});
NSLog(@"touchBegin----------End");
}