block的本质

  • block本质上也是一个OC对象,它内部也有个isa指针
  • block是封装了函数调用以及函数调用环境的OC对象

block的底层结构如图所示
image.png

block的变量捕获(capture)

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
image.png

block的类型

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

  • 全局块NSGlobalBlock ( _NSConcreteGlobalBlock )
    • 全局块存储在静态区(也叫全局区),相当于Objective-C中的单例
  • 栈块NSStackBlock ( _NSConcreteStackBlock )
    • 栈块存储在栈区,超出作用域则马上被销毁
  • 堆块NSMallocBlock ( _NSConcreteMallocBlock )
    • 堆块存储在堆区中,是一个带引用计数的对象,需要自行管理其内存。

image.png
image.png
每一种类型的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属性的建议写法

    1. @property (copy, nonatomic) void (^block)(void);
  • ARC下block属性的建议写法

    1. @property (strong, nonatomic) void (^block)(void);
    2. @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变量包装成一个对象
image.png

__block的内存管理

  • 当block在栈上时,并不会对__block变量产生强引用
  • 当block被copy到堆时
    • 会调用block内部的copy函数 copy函数内部会调用_Block_object_assign函数
    • _Block_object_assign函数会对__block变量形成强引用(retain)

image.png

  • 当block从堆中移除时
    • 会调用block内部的dispose函数 dispose函数内部会调用_Block_object_dispose函数
    • _Block_object_dispose函数会自动释放引用的__block变量(release)

image.png

block的forwarding指针

image.png

对象类型的auto变量、__block变量

  • 当block在栈上时,对它们都不会产生强引用
  • 当block拷贝到堆上时,都会通过copy函数来处理它们

    • __block变量(假设变量名叫做a)

      1. _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
    • 对象类型的auto变量(假设变量名叫做p)

      1. _Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
  • 当block从堆上移除时,都会通过dispose函数来释放它们

    • __block变量(假设变量名叫做a)

      1. _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
    • 对象类型的auto变量(假设变量名叫做p)

      1. _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时会retainMRC时不会retain
  • 如果__block变量从堆上移除
    • 会调用__block变量内部的dispose函数
    • dispose函数内部会调用_Block_object_dispose函数
    • _Block_object_dispose函数会自动释放指向的对象(release)

      __strong作用

当block内部调用了延时函数或并发执行时,需要用__strong再将弱指针重新引用,保证block执行完毕之前self不会被释放

  1. __weak __typeof(self) weakSelf = self;
  2. self.block = ^{
  3. __strong __typeof(self) strongSelf = weakSelf;
  4. [strongSelf doSomeThing];
  5. [strongSelf doOtherThing];
  6. };

注意:不会造成循环引用,是因为strongSelf实质是一个局部变量(在block这个“函数”里面的局部变量),当block执行完毕就会释放自动变量strongSelf,不会对self进行一直进行强引用。

__unsafe_unretained__weak区别

  • __unsafe_unretained 不安全引用:当对象销毁时,会依然指向之前的内存空间(野指针),如果使用此指针,程序会抛出 BAD_ACCESS 的异常。
  • __weak 弱引用:当对象销毁时,会自动指向nil

解决循环引用问题

ARC

  • __weak__unsafe_unretained解决

    1. //使用__weak ClassName
    2. __weak XXViewController* weakSelf = self;
    3. //使用__weak typeof(self)
    4. __weak typeof(self) weakSelf = self;
    5. //使用__unsafe_unretained
    6. __unsafe_unretained id weakSelf = self;

    image.png

  • __block解决

    1. __block XXController *blkSelf = self;
    2. self.blk = ^{
    3. NSLog(@"In Block : %@",blkSelf);
    4. blkSelf = nil;//不能省略
    5. };
    6. self.blk();//该block必须执行一次,否则还是内存泄露

    image.png

    注意:用__block解决(必须要调用block) 否则存在内存泄露。

  • 对象以Block参数形式传入

将在Block内要使用到的对象(一般为self对象),以Block参数的形式传入,Block就不会捕获该对象,而将其作为参数使用,其生命周期系统的栈自动管理,不造成内存泄露。

  • 简化了两行代码,更优雅
  • 更明确的API设计:告诉API使用者,该方法的Block直接使用传进来的参数对象,不会造成循环引用,不用调用者再使用weak避免循环
    1. self.blk = ^(UIViewController *vc) {
    2. NSLog(@"Use Property:%@", vc.name);
    3. };
    4. self.blk(self);

MRC

  • 用__unsafe_unretained解决
  • 用__block解决

    <br />

动画 block

  1. self.demoView.center = CGPointMake(self.view.center.x, 0);
  2. // 此方法会立即执行动画 block
  3. [UIView animateWithDuration:2.0 delay:0 usingSpringWithDamping:0.3 initialSpringVelocity:10 options:0 animations:^{
  4. NSLog(@"动画开始");
  5. self.demoView.center = self.view.center;
  6. } completion:^(BOOL finished) {
  7. // 会在动画结束后执行
  8. NSLog(@"动画完成");
  9. }];
  10. NSLog(@"come here");

使用系统提供的block api不需要考虑循环引用的,因为这里只有block对self进行了一次强引用,属于单向的强引用,没有形成循环引用。

面试题

block的原理是怎样的?本质是什么?

  • 封装了函数调用以及调用环境的OC对象

    block的属性修饰词为什么是copy?使用block有哪些使用注意?

  • block一旦没有进行copy操作,就不会在堆上

  • 使用注意:循环引用问题

    block在修改NSMutableArray,需不需要添加__block?

  • 不需要

    block和weak修饰符的区别

  1. __block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
  2. __weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。
  3. 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、指定运行时系统版本,比如
    1. xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m