这篇博文将介绍iOS中block的底层原理及其实现,并同时阐述下weak及 block的作用等。

  • block底层数据结构
  • block类型
  • block的循环引用

1 block底层数据结构

  • block本质上也是一个OC对象,它内部含有isa指针
  • block是封装了函数调用以及函数调用环境的OC对象
  • block底层结构:
  1. // 例如这样的一个block
  2. int age = 10;
  3. void(^block)(void) = ^{
  4. NSLog(@"age is %d",age);
  5. }
  6. // 底层结构是
  7. // 包含三部分:block的内部实现,block的描述信息,引用(捕捉的)成员信息
  8. struct __main_block_impl_0 {
  9. struct __block_impl impl; // block的内部实现信息
  10. struct __main_block_desc_0 *Desc; // block的描述信息
  11. int age; // 引用的属性列表
  12. }
  13. // block的实现部分
  14. struct __block_impl {
  15. void *isa; // isa指针
  16. int Flags;
  17. int Reserved;
  18. void *FuncPtr; // 方法地址,用例调用block封装的方法
  19. }
  20. // block的描述信息,注意,如果block捕捉的属性是对象信息,那这里会有block对对象的引用函数(即是强引用还是弱引用,以及如何进行内存管理)
  21. struc __main_block_desc_0 {
  22. size_t reserved;
  23. size_t Block_size; // block的大小
  24. }

2 block的类型

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

  • NSGlobalBlock(NSConcreteGlobalBlock):数据区域
  • NSStackBlock(NSConcreteStackBlock):栈区
  • NSMallocBlock(NSConcreteMallocBlock):堆区

应用程序的内存分配:程序区域(.text)、数据区域(.data)、堆、栈

block的类型会随着变量的访问发生转变

  • 没有访问auto变量——>NSGlobalBlock
  • 访问了auto变量——>NSStackBlock
  • NSStackBlock调用了copy——>NSMallockBlock

每一种block调用copy后

Block类型 副本源的配置存储域 copy后的效果
NSGlobalBlock 程序的数据区 什么也不做
NSStackBlock 栈区 从栈复制到堆
NSMallockBlock 堆区 引用计数加一

MRC下block属性建议写法:

  1. @property(copy, nonatomic) void(^block)(void);

即用属性修饰词copy而不用strong

因为用了copy会将block从栈区复制到堆区进行操作,防止获取错误信息。如果block在栈区,且引用的auto成员变量也在栈区,系统会自动销毁收回成员变量,这时候再访问就容易获取错误信息,而复制到堆上就不会,堆上的信息需要程序员自己销毁。

ARC环境下的block:

  • 建议写法:

    1. // 以下两中写法都可以,但为了同步MRC,建议用copy
    2. @property (strong, nonatomic) void (^block)(void);
    3. @property (copy, nonatomic) void (^block)(void);
  • 编译器会根据以下情况自动将栈上的block复制到堆上

    • block作为函数返回值时
    • 将block赋值给__strong指针时,即强引用时
    • block作为Cocoa API中方法名含有usingBlock的方法参数时
    • block作为GCD API的方法参数时

block引用对象类型的auto变量
**

  • 如果block在栈上,将不会对auto变量产生强引用
  • 如果block被拷贝到堆上:
    会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修饰符(__strong weak unsafe_unretained)作出相应的操作,形成强引用或弱引用
  • block从堆上移除:
    会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose_Block_object_dispose函数会自动释放引用的auto变量

__block修饰符
**

  • __block可以解决block内部无法修改auto变量的问题
  • __block不能修饰全局变量、静态变量(static)
  • block在栈上时,并不会对__block变量产生强引用
  • 内部原理:编译器会将block变量包装成一个对象,然后进行操作 ```objc // 简单的block block int age = 10; ^{ NSLog(@”age is %d”, age); }()

// 编译器编译后的block对象,将age包装成了Block_byref_age_0的对象 struct main_block_impl_0 { struct block_impl impl; struct main_block_desc_0 Desc; __Block_byref_age_0 age; // by ref }

// Block_byref_age_0:age包装后的对象 struct Block_byref_age_0 { void isa; __Block_byref_age_0 forwarding; // 指向自己的指针 int flags; int __size; int age; // 使用的值,真实值 }

  1. 内部修改block变量:拿到block对象的age,找到age中的**forwarding,再通过forwarding找到变量包装后的对象来修改里面的变量值:age->**forwarding->age = XXX.
  2. **注意:**当block被复制到堆上时,栈上的block里面的__forwarding指针指向堆上的block对象
  3. <a name="bfbd4433"></a>
  4. ### 3 block的循环引用
  5. - __weak
  6. ```objective-c
  7. __weak typeof(self)weakSelf = self;
  8. self.block = ^{
  9. printf("%p",weakSelf);
  10. }
  • 用__unsafe_unretained解决

    1. __unsafe_unretained id weakSelf = self;
    2. self.block = ^{
    3. printf("%p",weakSelf);
    4. }
  • 用__block解决(必须要调用block)

    1. __block id weakSelf = self;
    2. self.block = ^{
    3. printf("%p",weakSelf);
    4. weakSelf = nil;
    5. }
    6. self.block();
  • 注意:

建议用__weak而不建议用__unsafe_unretained,因为当引用用的对象释放时,如果用weak修饰的话,block中的指针会被设置成nil,而用__unsafe_unretained修饰的不会设置成nil,这样会导致野指针访问。

4 Tips

  • clang将oc转换成c\c++时,__weak问题解决
    需要支持ARC、指定运行时系统版本
    1. xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
    2. # 不加对比:
    3. xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m