一.block的本质
block本质是一个OC对象。封装了函数调用地址和函数调用环境(参数)
block本质
二.block的变量捕获(capture)
-
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
-
自动变量:局部变量默认会在前面加上auto修饰词,叫自动变量。
-
捕获原则如下:
block的变量捕获.png -
捕获到block内部:在block内部会新增一个局部变量来接收传进来的值
block内部接收 -
为什么局部变量需要捕获呢,全局变量就不需要?
因为block的调用和block的声明不在一个函数里面话。局部变量就获取不到了。为了在另一个函数里面可以调用到局部变量,所以需要捕获。
而全局变量在哪个函数都可以调用到,所以就不需要捕获 -
为什么auto修饰的是值传递呢?
auto修饰的是局部变量,出了方法会被释放掉,进行值传递就不会因为释放掉而出错。静态变量出了方法不会被释放。
捕获局部变量的原因
三.block的类型
1. block类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )
2. block内存分配
block内存分配3. 不同类型的block
image.png-
每一种类型的block调用copy后的结果如下所示
block 调用copy之后 -
函数调用完毕,栈上面的block会销毁。栈内存的数据就是垃圾数据。调用这个block,里面打印出来的内容就不确定
-
堆上面的block不会销毁,除非调用release
4.block的copy(block怎么从堆复制到栈上面)
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上
,比如以下情况
- block作为函数返回值时(如下图)
- 将block赋值给__strong指针时(赋值给强指针的时候)(如下图)
- block作为Cocoa API中方法名含有usingBlock的方法参数时(如下)
- block作为GCD API的方法参数时(如下)
注意,是对栈上的block进行以上操作才可以
堆上的block进行强引用.png
上面使用@property进行修饰,block中用了atuo变量,block在栈上,所以让栈上的block复制到堆上。
如果是未使用atuo变量,block是global类型的,无法复制到堆上。
-
MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void); -
ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);//因为在arc下强引用栈上面的block,会对block进行copy操作,从栈上复制到堆上??????
@property (copy, nonatomic) void (^block)(void);
为什么mrc下建议用copy、arc下建议用copy、strong呢?
因为对于堆上的block。
mrc下把block进行copy会放到堆上。arc下把blcok进行强引用,会复制到堆上。
放到栈上的block出了方法会被释放掉,堆上的不会。所以这么做。
5.block与对象类型的auto变量
上面讲到,block使用auto变量就是栈类型的。那么block内部访问的auto变量是对象类型,block内部对这个对象是强引用还是弱引用呢?
当block内部访问了对象类型的auto变量时:
如果block是在栈上,将不会对auto变量产生强引用
(参考图【栈上面block实例】)
如果block被拷贝到堆上
- 会调用block内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数
会根据auto变量的修饰符(__strong、?>__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
(参考【堆上的blcok强引用】、【堆上的blcok弱引用】)
如果block从堆上移除
- 会调用block内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量(release)
弱引用的对象,出了对象所在的自动释放池就释放了。
强引用的对象,和block一起销毁。block出了所在的block释放。具体看下面:
堆上的blcok弱引用
四.__block
- __block的作用:用于解决block内部无法修改auto变量值的问题
__block int age = 10;
MyBlock block = ^(){
age = 20;
NSLog(@"%d",age);
};
block();
__block Person * person = [[Person alloc] init];
MyBlock block1 = ^(){
person = [[Person alloc] init];
};
-
使用注意:__block不能修饰全局变量、静态变量(static)
-
__block本质/内部原理:
__block修饰的变量会被包装成一个对象。Block对象内部会有
包装好的对象的指针。 所以我们要修改__block修饰的变量时,
Block内部直接去找这个指针,找到要修改的值。
__block内部原理
__block内存管理
-
当block
在栈上时,并不会对__block变量产生强引用
-
当
block被copy到堆时
1.会调用block内部的copy函数
2.copy函数内部会调用_Block_object_assign函数
3._Block_object_assign函数会对__block变量形成强引用
(retain)
如下:
block0、block1在栈上的时候同时持有__block修饰的变量。
block0复制到堆上的时候,__block修饰的变量也复制到堆上、被强引用了。
image.png
block1复制到堆上的时候,因为持有的__block已经在堆上了,就继续指向原本的__blcok修饰的变量。
image.png
- 当block从堆中移除时
1.会调用block内部的dispose函数
2.dispose函数内部会调用_Block_object_dispose函数
3._Block_object_dispose函数会自动释放引用的__block变量(release)
内存管理总结
变量类型 | 是否捕获到内部 | 访问方式 |
---|---|---|
局部 auto修饰 | 是 | 值传递 |
局部 static修饰 | 是 | 指针传递 |
全局变量 | 否 | 直接访问 |
栈类型block | 堆类型block | |
---|---|---|
auto修饰的对象 | 弱引用 | 根据所指向对象的修饰符形成强引用(retain)或者弱引用 |
__block修饰的对象 | 弱引用 | 根据所指向对象的修饰符形成强引用(retain)或者弱引用 |
__block修饰的变量 | 弱引用 | 强引用 |
上面三者:auto修饰的对象、__block变量、__block修饰的对象:
当block拷贝到堆上时,都会通过copy函数来处理它们
当block从堆上移除时,都会通过dispose函数来释放它们
__block的__forwarding指针
上面有提到,__block修饰的变量会被包装成一个对象。
这个对象里面有__forwarding指针,是指向自身的_isa指针。
__block的_forwarding指针
当block从栈上复制到堆上,__block生成的对象也会复制到堆上。栈上的__block内部_forwarding指针会指向堆上的__block生成的对象。如下图:
image.png
五.循环引用
解决循环引用问题 - ARC
-
用__weak、__unsafe_unretained解决
循环引用
- 用__block解决(必须要调用block;)
必须:Block内部把引用的对象设置为nil,并且调用block。
因为__block生成的对象内部强引用对象,需要打破这个强引用。
__block解决方法1
因为上面强引用了,所以直接采用弱引用,发现也解决循环引用的问题了:
__block解决方法2
解决循环引用问题 - MRC
-
用__unsafe_unretained解决(mrc里面没有_weak)
__unsafe_unretained解决 -
用__block解决 (
image.pngmrc下不会对__block的对象进行强引用
,所以mrc下不需要置位nil)
六.面试问题
-
block的原理是怎样的?本质是什么?
封装了函数调用以及调用环境的OC对象 -
__block的作用是什么?有什么使用注意点?
解决block内部无法修改auto变量的问题。把__block修饰的变量封装成一个对象,放在block内部。
注意点:
__block会进行相应的内存管理;(比如堆上的block,会根据__block对象的修饰符进行强引用或者弱引用)
mrc环境下,不会对__block修饰的变量进行强引用;
-
block的属性修饰词为什么是copy?使用block有哪些使用注意?
block一旦没有进行copy操作,就不会在堆上;放在堆上是为了控制block的生命周期。
使用注意:循环引用问题 -
block在修改NSMutableArray,需不需要添加__block?
为数组增删改的时候不需要。
修改指针的对象的时候需要。
网友评论