@iwanglong
2019-01-07T09:49:05.000000Z
字数 5791
阅读 407
iOS
现在面试中肯定会问到block相关的知识点,平时用block的地方也还是挺多的,为了很好的应对面试,这里将一些常问的都整理一下。
- block原理是怎样的,本质是什么?
- __block的作用是什么?有什么使用注意点?
- block的属性修饰词为什么是copy?使用block有哪些使用注意?
- block在修改NSMutableArray,需不需要添加__block?
我们带着问题来研究一下:
block本质上也是一个oc对象,他内部也有一个isa指针。block是封装了函数调用以及函数调用环境的OC对象。
- isa指针在哪儿呢?
我们通过OC代码转成C++代码查看源码可以知道,block定义中调用__main_block_imp_0函数,这个结构体中包含结构体__block_impl、结构体__main_block_desc_0、以及自定义的变量age。以及一个__main_block_imp_0函数; 那么isa在哪儿呀,就在__block_impl这个结构体中啦,这个结构体中存放着将block代码块中代码封装成__main_block_func_0函数的地址- 本质
本质就是__main_block_impl_0结构体类型。

总结:局部变量都会被block捕获,自动变量是值捕获,静态变量为地址捕获。全局变量则不会被block捕获
__NSGlobalBlock_ _ ( _NSConcreteGlobalBlock )
__NSStackBlock_ _ ( _NSConcreteStackBlock )
__NSMallocBlock_ _ ( _NSConcreteMallocBlock )


一旦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变量。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{Person *p = [[Person alloc] init];dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"%@",p);});NSLog(@"-------touchesBegan------end");}2019-01-07 10:20:09.874108+0800 Autoreleasepool[33413:2118152] -------touchesBegan------end2019-01-07 10:20:13.169933+0800 Autoreleasepool[33413:2118152] <Person: 0x60800000db40>2019-01-07 10:20:13.170205+0800 Autoreleasepool[33413:2118152] ----Person-----dealloc
在ARC环境中,block作为GCD API的方法参数时会自动进行copy操作,因此block在堆空间,并且使用强引用访问person对象,因此block内部copy函数会对person进行强引用。当block执行完毕需要被销毁时,调用dispose函数释放对person对象的引用,person没有强指针指向时才会被销毁。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{Person *p = [[Person alloc] init];__weak Person *weakP = p;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"%@",weakP);});NSLog(@"-------touchesBegan------end");}2019-01-07 10:24:19.531902+0800 Autoreleasepool[33621:2121142] -------touchesBegan------end2019-01-07 10:24:19.532074+0800 Autoreleasepool[33621:2121142] ----Person-----dealloc2019-01-07 10:24:22.531949+0800 Autoreleasepool[33621:2121142] (null)
block中对waekP为__weak弱引用,因此block内部copy函数会对person同样进行弱引用,当大括号执行完毕时,person对象没有强指针引用就会被释放。因此block块执行的时候打印null。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{Person *p = [[Person alloc] init];__weak Person *weakP = p;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"------weakP-----:%@",weakP);dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"------p-----:%@",p);});});NSLog(@"-------touchesBegan------end");}2019-01-07 10:25:51.412902+0800 Autoreleasepool[33705:2122604] -------touchesBegan------end2019-01-07 10:25:52.508281+0800 Autoreleasepool[33705:2122604] ------weakP-----:<Person: 0x60400000e110>2019-01-07 10:25:55.508562+0800 Autoreleasepool[33705:2122604] ------p-----:<Person: 0x60400000e110>2019-01-07 10:25:55.508812+0800 Autoreleasepool[33705:2122604] ----Person-----dealloc
person对象在4s后销毁,在第二个dispatch_after内部还在强引用person对象,因此需要在第二个block执行完毕以后才会对person对象释放。作为对比,请看下边的例子
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{Person *p = [[Person alloc] init];__weak Person *weakP = p;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"------p-----:%@",p);dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"------weakP-----:%@",weakP);});});NSLog(@"-------touchesBegan------end");}2019-01-07 10:29:31.948325+0800 Autoreleasepool[33890:2125189] -------touchesBegan------end2019-01-07 10:29:33.045081+0800 Autoreleasepool[33890:2125189] ------p-----:<Person: 0x60400001bd80>2019-01-07 10:29:33.045356+0800 Autoreleasepool[33890:2125189] ----Person-----dealloc2019-01-07 10:29:36.045392+0800 Autoreleasepool[33890:2125189] ------weakP-----:(null)
person对象1s后销毁了,所以第二个block内打印的对象为null。
平时用的时候咱们都知道加上__block修饰就行了啊,是的,没错。
那么为啥加上__block就能修改了呢?
其实除了__block外,还可以使用static修饰,因为static修饰的age变量传递到block内部的是指针,在__main_block_func_0函数内部就可以拿到age变量的内存地址,因此就可以在block内部修改age的值。
当然,还是说一下__block吧,__block用于解决block内部不能修改auto变量值的问题,__block不能修饰静态变量(static) 和全局变量。编译器会将__block修饰的变量包装成一个对象。__block将变量包装成对象,然后在把age封装在结构体里面,block内部存储的变量为结构体指针,也就可以通过指针找到内存地址进而修改变量的值。
上面提到过__block修饰的age变量在编译时会被封装为结构体,那么当在外部使用age变量的时候,使用的是__Block_byref_age_0结构体呢?还是__Block_byref_age_0结构体内的age变量呢?
当block内存在栈上时,并不会对__block变量产生内存管理。当blcok被copy到堆上时
会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用(相当于retain)
首先通过一张图看一下block复制到堆上时内存变化
__weak修饰的对象被Block引用,不会影响对象的释放,而__strong在Block内部修饰的对象,会保证,在使用这个对象在scope内,这个对象都不会被释放,出了scope,引用计数就会-1,且__strong主要是用在多线程运用中,若果只使用单线程,只需要使用__weak即可
__weak __strong 在block中使用
OC中Block使用了__weak和__strong依然不会循环引用原因