Block的本质

894 阅读5分钟
  • 1.block的原理是怎样的?本质是什么?
  • 2.__block的作用是什么?有什么使用注意点?
  • 3.block的属性修饰词为什么是copy?使用block有哪些使用注意?
  • 4.block在修改NSMutableArray,需不需要添加__block?

block的本质上也是一个OC对象,他内部有一个isa指针。block是封装了函数和函数调用的环境的oc对象。

block底层是一个结构体,内部包含impl(结构体),desc(结构体),以及捕获的变量。 impl包含isa指针、FuncPtr desc包含了:block_size、copy、dispose.(堆上的block会有copy和dispose操作) 也可以这么理解:

图片1.png

局部变量: auto类型: 默认类型,离开作用域会自动销毁,会被捕获到block内部,是值传递, 例如age
static类型:静态变量, 会一直存在内存中,被block捕获,是指针传递。
全局变量:不会捕获,直接访问

image.png

屏幕快照 2021-08-12 下午11.31.19.png

block的类型: NSGlobalBlock(没有访问任何auto变量)、NSStackBlock(访问了auto变量)、NSMallocBlock(栈上的block调用了copy操作) 很少用到global类型的block,因为这样的block没有意义,不需要访问任何外界变量,完全可以用函数替代。 stack类型的block存在于栈区,内存有系统自由分配和释放,出了作用域执行完毕就会被销毁,而在相同作用域定义并调用block似乎也没什么必要。存在问题:我们很可能在block销毁之后才去调用它。栈上的block不会对捕获的对象强引用 malloc类型的block,是我们最常用到的,存放在堆区,需要我们自己对内存进行管理。栈上的block调用copy操作,就被拷贝到堆区。 所以我们平常开发过程中,MRC环境经常把栈上的block拷贝到堆区,保证block不会被销毁,需要我们自己调用release进行销毁。ARC下,系统会自动调用copy操作,使得block不被销毁。

image.png

屏幕快照 2021-08-12 下午11.33.23.png

什么情况下ARC会自动将block进行copy操作?首先是栈上的block(访问了auto变量)

  • 1.block作为函数返回值
  • 2.将block赋值给__strong指针时,也就是block被强指针引用时
  • 3.block作为Cocoa API中方法名含有usingBlock的方法参数时:
  • 例如遍历数组[array enumerataObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL stop) {
  • }]
  • 4.block作为GCD API的方法参数时
  • 例如延迟函数:dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ });

block声明的写法:

MRC下: @property (copy, nonatomic) viod(^block)(void)
ARC下: @property (copy, nonatomic) viod(^block)(void)或
@property (strong, nonatomic) viod(^block)(void)

block被拷贝到堆上内部实现:

当block被拷贝到堆上时,会调用desc中的copy函数,copy函数内部又会调用_Block_object_assign函数。

  • 1.有捕获对象类型的auto变量,_Block_object_assign函数会根据对象类型的auto变量的修饰符:__strong, __weak, __unsafe_unretained来产生强引用或者弱引用,类似于retain操作,但是如果block位于栈上,不会对捕获的对象产生强引用,不论捕获的是__strong还是__weak修饰
  • 2.有捕获__block值类型修饰的变量的时候:_Block_object_assign函数会对 __block 值类型变量形成强引用(retain引用计数操作)。Block拷贝到堆上的同时,也会把捕获的 __block值类型变量同时从栈上拷贝到堆上。
  • 3.有捕获__block 对象类型变量的时候:_Block_object_assign函数会根据对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,对 __block 对象类型变量形成强应用(retain)或弱引用(ARC会retain,MRC不会retain)。 如果 block从堆上移除,会调用desc中的dispose函数,dispose函数内部又会调用__Block_object_dispose函数,__Block_object_dispose函数会自动释放引用的对象类型auto变量,类似于release操作。 有捕获 __block变量的时候,__Block_object_dispose函数会自动释放引用的__block 变量(release-引用计数操作)。

__block的作用

  • __block用来解决block内部无法修改auto变量值得问题
  • __block修饰变量:编译器会把修饰的变量包装成一个对象,__Block_byref_var_0 结构体,里面包含了isa指针、__forwarding、变量、以及内存管理的两个函数copy和dispose。当局部变量的值不需要修改的时候,尽量不要添加__block,因为一旦添加了__block,系统会创建对应的结构体,占用不必要的内存空间。
  • __block不能修饰全局变量、静态变量(static)
  • __forwarding指针指向的是结构体自己,当使用变量的时候,通过结构体找到__forwarding指针,在通过__forwarding指针找到相应的变量。这样设计的目的是为了方便内存管理,因为block一开始是在栈上,会被拷贝到堆上,捕获的__block修饰的变量也会一起被拷贝到堆上,在block内部修改捕获的变量的时候,为了防止修改的是原来栈上的变量,栈上的block中捕获对象的结构体的__forwarding指针指向的是堆中的结构体,堆中block中捕获对象的结构体的__forwarding指针指向的还是他自己。通过__forwarding修改值得时候,修改的就是堆上的变量了。
  • MRC下__block可以解决循环引用,block虽然执行了copy操作,但是MRC下__block的结构体不会对对象强引用,依然是弱引用,所以可以解决循环引用。

解决循环引用:

  • ARC下使用__weak或者__unsafe_unretained修饰对象
  • MRC下使用__block或者__unsafe_unretained修饰对象

__weak和__unsafe_unretained的区别

__weak不会产生强引用,指向的对象销毁是,会自动将指针置为nil,一般推荐这种方式。 __unsafe_unretained不会产生强引用,不安全,对象销毁的时候,指针存储的内容不会变化。

__strong和__weak

__weak typeof(self) weakself = self;
person.block = ^ {
    __strong typeof(weakself) strongself = weakself;
    NSLog(@"age is %d", strongself->_age);
}

在block内部重新使用__strong修饰self变量也是为了在block内部有一个强指针指向weakself,避免在block调用的时候,weakself已经被销毁。

友情感谢这个作者:juejin.cn/post/684490…