block的本质
- block本质上也是一个OC对象,它内部也有个isa指针
- block是封装了函数调用以及函数调用环境的OC对象
block的底层结构如图所示
block的变量捕获(capture)
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
block的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
- 全局块NSGlobalBlock ( _NSConcreteGlobalBlock )
- 全局块存储在静态区(也叫全局区),相当于Objective-C中的单例
- 栈块NSStackBlock ( _NSConcreteStackBlock )
- 栈块存储在栈区,超出作用域则马上被销毁
- 堆块NSMallocBlock ( _NSConcreteMallocBlock )
- 堆块存储在堆区中,是一个带引用计数的对象,需要自行管理其内存。
每一种类型的block调用copy后的结果如下所示
block的类型 | 存储区域 | 复制效果 |
---|---|---|
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteGlobalBlock | 程序的数据区域 | 什么也不做 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |
block的copy
copy修饰符的作用就是将block从栈区拷贝到堆区,主要目的就是保存block的状态,延长其生命周期。
在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况
- block作为函数返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
MRC下block属性的建议写法
@property (copy, nonatomic) void (^block)(void);
ARC下block属性的建议写法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
对象类型的auto变量
当block内部访问了对象类型的auto变量时
如果block是在栈上,将不会对auto变量产生强引用
- 如果block被拷贝到堆上
- 会调用block内部的copy函数
- copy函数内部会调用
_Block_object_assign
函数 _Block_object_assign
函数会根据auto变量的修饰符(strong、weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
- 如果block从堆上移除
- 会调用block内部的dispose函数
- dispose函数内部会调用
_Block_object_dispose
函数 _Block_object_dispose
函数会自动释放引用的auto变量(release)
函数 | 调用时机 |
---|---|
copy函数 | 栈上的Block被复制到堆上时 |
dispose函数 | 堆上的Block被废弃时 |
__block
修饰符
__block
可以用于解决block内部无法修改auto变量值的问题__block
不能修饰全局变量、静态变量(static)
本质
__block的内存管理
- 当block在栈上时,并不会对__block变量产生强引用
- 当block被copy到堆时
- 会调用block内部的copy函数 copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会对__block变量形成强引用(retain)
- 当block从堆中移除时
- 会调用block内部的dispose函数 dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放引用的__block变量(release)
block的forwarding指针
对象类型的auto变量、__block变量
- 当block在栈上时,对它们都不会产生强引用
当block拷贝到堆上时,都会通过copy函数来处理它们
__block变量(假设变量名叫做a)
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
对象类型的auto变量(假设变量名叫做p)
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
当block从堆上移除时,都会通过dispose函数来释放它们
__block变量(假设变量名叫做a)
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
对象类型的auto变量(假设变量名叫做p)
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
| 对象 | BLOCK_FIELD_IS_OBJECT | | —- | —- | | __Block变量 | BLOCK_FIELD_IS_BYREF |
被__block
修饰的对象类型
- 当
__block
变量在栈上时,不会对指向的对象产生强引用 - 当
__block
变量被copy到堆时- 会调用
__block
变量内部的copy
函数 copy
函数内部会调用_Block_object_assign
函数_Block_object_assign
函数会根据所指向对象的修饰符(__strong
、__weak
、__unsafe_unretained
)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC
时会retain
,MRC
时不会retain
)
- 会调用
- 如果__block变量从堆上移除
当block内部调用了延时函数或并发执行时,需要用__strong
再将弱指针重新引用,保证block执行完毕之前self不会被释放
__weak __typeof(self) weakSelf = self;
self.block = ^{
__strong __typeof(self) strongSelf = weakSelf;
[strongSelf doSomeThing];
[strongSelf doOtherThing];
};
注意:不会造成循环引用,是因为strongSelf实质是一个局部变量(在block这个“函数”里面的局部变量),当block执行完毕就会释放自动变量strongSelf,不会对self进行一直进行强引用。
__unsafe_unretained
和__weak
区别
__unsafe_unretained
不安全引用:当对象销毁时,会依然指向之前的内存空间(野指针),如果使用此指针,程序会抛出BAD_ACCESS
的异常。__weak
弱引用:当对象销毁时,会自动指向nil
解决循环引用问题
ARC
用
__weak
、__unsafe_unretained
解决//使用__weak ClassName
__weak XXViewController* weakSelf = self;
//使用__weak typeof(self)
__weak typeof(self) weakSelf = self;
//使用__unsafe_unretained
__unsafe_unretained id weakSelf = self;
用
__block
解决__block XXController *blkSelf = self;
self.blk = ^{
NSLog(@"In Block : %@",blkSelf);
blkSelf = nil;//不能省略
};
self.blk();//该block必须执行一次,否则还是内存泄露
注意:用__block解决(必须要调用block) 否则存在内存泄露。
- 对象以Block参数形式传入
将在Block内要使用到的对象(一般为self对象),以Block参数的形式传入,Block就不会捕获该对象,而将其作为参数使用,其生命周期系统的栈自动管理,不造成内存泄露。
- 简化了两行代码,更优雅
- 更明确的API设计:告诉API使用者,该方法的Block直接使用传进来的参数对象,不会造成循环引用,不用调用者再使用weak避免循环
self.blk = ^(UIViewController *vc) {
NSLog(@"Use Property:%@", vc.name);
};
self.blk(self);
MRC
动画 block
self.demoView.center = CGPointMake(self.view.center.x, 0);
// 此方法会立即执行动画 block
[UIView animateWithDuration:2.0 delay:0 usingSpringWithDamping:0.3 initialSpringVelocity:10 options:0 animations:^{
NSLog(@"动画开始");
self.demoView.center = self.view.center;
} completion:^(BOOL finished) {
// 会在动画结束后执行
NSLog(@"动画完成");
}];
NSLog(@"come here");
使用系统提供的block api不需要考虑循环引用的,因为这里只有block对self进行了一次强引用,属于单向的强引用,没有形成循环引用。
面试题
block的原理是怎样的?本质是什么?
-
block的属性修饰词为什么是copy?使用block有哪些使用注意?
block一旦没有进行copy操作,就不会在堆上
-
block在修改NSMutableArray,需不需要添加__block?
-
block和weak修饰符的区别
- __block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
- __weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
- block对象可以在block中被重新赋值,weak不可以。
PS:unsafe_unretained修饰符可以被视为iOS SDK 4.3以前版本的weak的替代品,不过不会被自动置空为nil。所以尽可能不要使用这个修饰符
问题
__weak问题解决
- 在使用clang转换OC为C++代码时,可能会遇到以下问题
cannot create __weak reference in file using manual reference
- 解决方案:支持ARC、指定运行时系统版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m