https://www.jianshu.com/p/25a7ba546eac
https://www.jianshu.com/p/4e79e9a0dd82
看了"小胖白兔"的两篇关于Block的文章,感觉获益良多。
再次总结一下阅读笔记,便于自己查看
1、Block是什么
Block本质上也是一个OC对象,是封装了函数调用以及函数调用上下文环境的OC对象
2、Block有哪几种
Block有哪几种类型
Block的类型,取决于isa指针,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
- __NSGlobalBlock __ ( _NSConcreteGlobalBlock ) 全局块
- __NSStackBlock __ ( _NSConcreteStackBlock ) 栈块
- __NSMallocBlock __ ( _NSConcreteMallocBlock ) 堆块
三种block各自的存储域:
-
全局块存在于全局内存中, 相当于单例.
-
栈块存在于栈内存中, 超出其作用域则马上被销毁
-
堆块存在于堆内存中, 是一个带引用计数的对象, 需要自行管理其内存
如何判断block是哪种类型?
-
没有访问auto变量的block是__NSGlobalBlock __ ,放在数据段(可以访问全局或者静态变量)
-
访问了auto变量的block是__NSStackBlock __
-
[__NSStackBlock __ copy]操作就变成了__NSMallocBlock __
注:copy操作可能会隐式调用
3、ARC下,访问外界变量的 Block为什么要自动从栈区拷贝到堆区呢?
栈上的Block,在其所属的变量作用域结束时,该Block就被废弃,如同一般的自动变量。当然,Block中的__block变量也同时被废弃。
为了解决栈块在其变量作用域结束之后被废弃(释放)的问题,我们需要把Block复制到堆中,延长其生命周期。开启ARC时,大多数情况下编译器会恰当地进行判断是否有需要将Block从栈复制到堆,如果有,自动生成将Block从栈上复制到堆上的代码。Block的复制操作执行的是copy实例方法。Block只要调用了copy方法,栈块就会变成堆块。
什么时候copy
-
在ARC的Block是配置在栈上的,所以返回函数调用方时,Block变量作用域就结束了,Block会被废弃。种情况编译器会自动完成复制。
-
在非ARC情况下则需要开发者调用copy方法手动复制。
-
将Block从栈上复制到堆上相当消耗CPU,所以当Block设置在栈上也能够使用时,就不要复制了,因为此时的复制只是在浪费CPU资源。
Block的复制操作执行的是copy实例方法。不同类型的Block使用copy方法的效果如下表:
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上的几种情况?
- 1.block作为函数返回值时
- 2.将block赋值给__strong指针时
- 3.block作为Cocoa API中方法名含有usingBlock的方法参数时
- 4.block作为GCD API的方法参数时
block变量与__forwarding
在copy操作之后,既然__block变量也被copy到堆上去了, 那么访问该变量是访问栈上的还是堆上的呢?__forwarding终于要闪亮登场了。通过/__forwarding, 无论是在block中还是 block外访问__block变量, 也不管该变量在栈上或堆上, 都能顺利地访问同一个__block变量。
__forwarding存在的意义就是:无论在栈还是堆上,forwarding都可以顺利访问到同一个__block变量
__栈上__block的__forwarding指向本身
栈上__block复制到堆上后,栈上block的__forwarding指向堆上的block,堆上block的__forwarding指向本身
Block 循环引用
Block 循环引用的情况:
某个类将 block 作为自己的属性变量,然后该类在 block 的方法体里面又使用了该类本身。
ARC 下使用三种方式:__weak、__unsafe_unretained、__block
MRC 下使用:__unsafe_unretained、__block
解决办法:
(1)使用 __weak
__weak typeof(self) weakSelf = self;
self.someBlock = ^(Type var){
[weakSelf dosomething];
};
(2)使用 __block
__block typeof(self) blockSelf = self;
self.someBlock = ^(Type var){
[blockSelf dosomething];
};
(3) 使用__unsafe_unretained
__unsafe_unretained Person *person = [[Person alloc] init];
person.block = ^{
NSLog(@"age is %d", weakPerson.age);
};
.三种方法比较
__weak
:不会产生强引用,指向的对象销毁时,会自动让指针置为nil__unsafe_unretained
:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变__block
:必须把引用对象置位nil,并且要调用该block
什么情况使用__block修饰符?
一般情况下,对被截获变量进行 赋值 操作需要添加__block修饰符
赋值 != 使用
赋值操作需要使用 __block
修饰
__block NSMutableArray *arrM = nil;
void (^testBlock)(void) = ^{
arrM = [NSMutableArray array];
};
testBlock();
**使用:**如下代码不需要使用__block
,因为是对数组的操作而不是数组的赋值。
NSMutableArray *arrM = [NSMutableArray array];
void (^testBlock)(void) = ^{
[arrM addObject:@"addObj"];
};
testBlock();
那么使用修饰符 __block以后发生了什么呢?
__block修饰变量变成了对象
__block
的内存管理
当block在栈上时,并不会对__block变量产生强引用
block的属性修饰词为什么是copy?
block一旦没有进行copy操作,就不会在堆上
block在堆上,程序员就可以对block做内存管理等操作,可以控制block的生命周期
当block被copy到堆时,对__block修饰的变量做了什么?
- 会调用block内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会对__block变量形成强引用(retain)
- 对于__block 修饰的变量 assign函数对其强引用;对于外部对象 assign函数根据外部如何引用而引用
Block 优缺点
优点:
- 捕获外部变量
- 降低代码分散程度
缺点:
- 循环引用引起内存泄露
Block 总结
- 在block内部使用的是将外部变量的拷贝到堆中的(基本数据类型直接拷贝一份到堆中,对象类型只将在栈中的指针拷贝到堆中并且指针所指向的地址不变)。
- __block修饰符的作用:是将block中用到的变量,拷贝到堆中,并且外部的变量本身地址也改变到堆中。
- __block不能解决循环引用,需要在block执行尾部将变量设置成nil
- __weak可以解决循环引用,block在捕获weakObj时,会对weakObj指向的对象进行弱引用。
- 使用__weak时,可在block开始用局部__strong变量持有,以免block执行期间对象被释放。
- 全局块不引用外部变量,所以不用考虑。
- 堆块引用的外部变量,不是原始的外部变量,是拷贝到堆中的副本。
- 栈块本身就在栈中,引用外部变量不会拷贝到堆中。
- __weak 本身是可以避免循环引用的问题的,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个 __strong 的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题。
- __block 本身无法避免循环引用的问题,但是我们可以通过在 block 内部手动把 blockObj 赋值为 nil 的方式来避免循环引用的问题。另外一点就是 __block 修饰的变量在 block 内外都是唯一的,要注意这个特性可能带来的隐患。
- block的实现原理是C语言的函数指针。函数指针即函数在内存中的地址,通过这个地址可以达到调用函数的目的。