OC底层-Block本质(五、捕获的变量何时销毁)
2020/7/1 23:26:21
本文主要是介绍OC底层-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 --- dealloc } return 0; } 复制代码
大括号执行完毕之后,person不会被释放。,person为auto变量,传入的block的变量同样为person,即block有一个强引用引用person,所以block不被销毁的话,person也不会销毁。
查看源代码确实如此
将上述代码转移到MRC环境下,在MRC环境下即使block还在,person却被释放掉了。因为MRC环境下block在栈上,栈上的block对外面的person不会进行强引用。
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 release]; // Person --- dealloc } NSLog(@"--------"); } return 0; } block调用copy操作之后,person不会被释放。 block = [^{ NSLog(@"------block内部%d",person.age); } copy]; 复制代码
只需要对栈空间的block进行一次copy操作,将栈空间的block会拷贝到堆中,person就不会被释放,说明堆空间的block可能会对person进行一次retain操作,以保证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 iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
__main_block_copy_0 和 __main_block_dispose_0
当block中捕获对象类型的变量时,我们发现block结构体__main_block_impl_0的描述结构体__main_block_desc_0中多了两个参数copy和dispose函数,查看源码:
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_0函数内部会调用_Block_object_dispose函数。
_Block_object_dispose会对person对象做释放操作,类似于release,也就是断开对person对象的引用,而person究竟是否被释放还是取决于person对象自己的引用计数。
总结
一旦block中捕获的变量为对象类型,block结构体中的__main_block_desc_0会出两个参数copy和dispose。因为访问的是个对象,block希望拥有这个对象,就需要对对象进行引用,也就是进行内存管理的操作。比如说对对象进行retarn操作,因此一旦block捕获的变量是对象类型就会会自动生成copy和dispose来对内部引用的对象进行内存管理。 当block内部访问了对象类型的auto变量时,如果block是在栈上,block内部不会对person产生强引用。不论block结构体内部的变量是__strong修饰还是__weak修饰,都不会对变量产生强引用。 如果block被拷贝到堆上。copy函数会调用_Block_object_assign函数,根据auto变量的修饰符(__strong,__weak,unsafe_unretained)做出相应的操作,形成强引用或者弱引用 如果block从堆中移除,dispose函数会调用_Block_object_dispose函数,自动释放引用的auto变量。 复制代码
疑问
person何时销毁
1、
- (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"); } 复制代码
2、
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { Person *person = [[Person alloc] init]; __weak Person *waekP = person; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"%@",waekP); }); NSLog(@"touchBegin----------End"); } 复制代码
3、
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { Person *person = [[Person alloc] init]; __weak Person *waekP = person; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"weakP ----- %@",waekP); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"person ----- %@",person); }); }); NSLog(@"touchBegin----------End"); } 复制代码
4、
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { Person *person = [[Person alloc] init]; __weak Person *waekP = person; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"person ----- %@",person); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"weakP ----- %@",waekP); }); }); NSLog(@"touchBegin----------End"); } 复制代码
总结疑问
1、block作为GCD API的方法参数时会自动进行copy操作,因此block在堆空间,并且使用强引用访问person对象,因此block内部copy函数会对person进行强引用。当block执行完毕需要被销毁时,调用dispose函数释放对person对象的引用,person没有强指针指向时才会被销毁。
故:在3秒后销毁person对象
2、block中对waekP为__weak弱引用,因此block内部copy函数会对person同样进行弱引用,当大括号执行完毕时,person对象没有强指针引用就会被释放。因此block块执行的时候打印null。
故:立马销毁person对象
第三个和第四个大家可自行验证。
这篇关于OC底层-Block本质(五、捕获的变量何时销毁)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值