iOS block内存管理

「这是我参与11月更文挑战的第27天,活动详情查看:2021最后一次更文挑战」。

__block 修饰符的使用

__block可以用于解决block内部无法修改auto变量值的问题

__block不能修饰全局变量、静态变量(static)

编译器会将__block变量包装成一个对象

以下代码的是否编译通过,可以的话输出结果是什么

int a = 10;
void (^block)() = ^{
    a = 20;
    NSLog(@"a = %d",a);
};
复制代码

结果:无法编译 miss__block
源码如下

//main函数
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        
        int a = 10;
        void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    }
    return 0;
}

//block执行地址
  static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_kh_0rp73c0s2mvfp5gjf25j5y6h0000gn_T_main_1a12fa_mi_0,a);}
复制代码

block执行的时候,内部是 __main_block_func_0 函数,而a的声明,是在main函数,两个函数相互独立,对于他们来说,a都是一个局部变量,而且两个函数中都对a初始化,两个函数的中a不是同一个,那怎么可以在 执行函数中,修改main函数中的局部变量呢,所以编译报错!
如何改?

方案一:使用static

static int a = 10;
void (^block)() = ^{
    a = 20;
    NSLog(@"a = %d",a);
};
复制代码

因为static修饰的auto变量,最终在block中进行的不是值传递,而是地址传递,措意执行函数中的a 和 main 函数中的a,是同一个地址 ==> 等于同一个a,所以可以修改,输出20

但是使用static,就会变成静态变量,永远在内存中
方案二: 使用__blcok

扫描二维码关注公众号,回复: 13462959 查看本文章
__block auto int a = 10;
void (^block)() = ^{
    a = 20;
    NSLog(@"a = %d",a);
};
复制代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref ==> auto的话,是int a__block,变成对象了
}
复制代码
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;==> 指向自己的结构体
 int __flags;
 int __size;
 int a; ==> 10在这里
};
复制代码

a = 20;最终转成 (a->__forwarding->a) = 20;

解释下:__forwarding 是指向结构体本身的指针,等价于a本身,其实就是通过a的结构体指针,拿到里面的成员a,再对他赋值

指针传递,所以可以修改 auto 变量,通过block,间接引用 auto 变量

image.png

__block中的 _ forwarding 指针

内存拷贝的时候,如果block从栈被copy到堆上,肯定也希望内部的变量一起存储到堆上(让变量的生命周期可控,才不会被回收)

加入变量a在栈上,在栈上的指针,指向堆上的 block,堆上的block的 forwarding指向他自己,就可以保证,修改&获取的变量,都是堆上的变量

最终,__block指向的变量,是指向堆上的

image.png

一旦block里面要用到/访问某个对象,那就要对相应的对象进行内存管理

当block在栈上的时候,是不会对__block变量进行强引用的
当block在堆上的时候,会调用block内部的copy函数,而copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修饰符 ( strong、 weak、unsafe_unretained ) 做出对应的操作,看对内部auto变量进行强引用还是弱引用

对象类型的auto变量和__block变量
当block在栈上的时候,对他们都不会产生强引用,永远都是弱引用,
当block被copy到堆上来的时候,都会通过copy函数来处理它们_Block_object_assign-》1.weak 2.strong

image.png

移除:
当block从堆中移除的时候,会调用block内部的dispose函数,而dispose函数内部会调用_Block_objcet_dispose函数,_Block_object_dispose 类似于 release,会对auto变量进行自动释放(当引用计数器=0的时候 )
上图

image.png

Tips:
在使用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

猜你喜欢

转载自juejin.im/post/7035934274423357477