Block探索

465 阅读21分钟
  • Block可以看作匿名函数,我们可以和函数一样声明和使用Block
  • 可以将Block作为参数进行传递,相较于传递函数指针,Block要更直观
  • 相比于函数Block更容易捕获上下文,同样也可能带来更多问题,比如循环引用

Block在我们的工程中很常见,正确合理的使用Block可以使我们的代码更加的简洁直观,可读性更高,若是对Block认识不足也可能导致各种问题。

Block结构

int autoVar = 10;
void (^myBlock)(int) = ^(int a){
    NSLog(@"%d-%d",autoVar,a);
};
myBlock(20);

通过xcrun转换为底层代码

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

这时在main.m边上出现了main.cpp打开并找到main函数

 int autoVar = 10;
 void (*myBlock)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, autoVar));
 ((void (*)(__block_impl *, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 20);

__main_block_impl_0

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int autoVar;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _autoVar, int flags=0) : autoVar(_autoVar) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

本例中myBlock本质就是一个__main_block_impl_0类型的结构体指针,包含

  • __block_impl类型结构体的
  • __main_block_desc_0类型的结构体指针
  • autoVar为捕获的外部变量,注意这里是值传递

__block_impl

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
  • isa指针说明__block_implOC对象类型
  • Flag标志位,后面会涉及
  • Reserved保留字端,暂时用不到
  • FuncPtr函数指针,本例中对应__main_block_func_0,也就是Block括号中的代码

__main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a) {
  int autoVar = __cself->autoVar; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_rd9sd5vj7_l6z9w7rpcd83g80000gn_T_main_fe6cad_mi_0,autoVar,a);
 }
  • __cself其实就是结构体__main_block_impl_0的指针

__main_block_desc_0

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
  • Block_size其值为block的大小sizeof(struct __main_block_impl_0)

main函数中类型修饰代码删除

int autoVar = 10;
 myBlock = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, autoVar);
 ((__block_impl *)myBlock)->FuncPtr((__block_impl *)myBlock, 20);

这里注意声明时myBlock的值为__main_block_impl_0类型的结构体指针,在执行时被强转成了__block_impl *类型,这是因为结构体__main_block_impl_0的第一个成员为struct __block_impl impl,所以impl__main_block_impl_0的地址相同。继续删除类型修饰代码

int autoVar = 10;
myBlock = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, autoVar);
myBlock->FuncPtr(myBlock, 20);

image.png

值类型捕获

Block在使用时可以捕获上下文信息,这是使用Block比较方便的地方同时也是容易出问题的地方,不同类型的外部数据在Block内部的实现也有所不同

局部变量/静态局部变量

int autoVar = 10;
static int staticVar = 20;
void (^myBlock)(void) = ^(){
    NSLog(@"%d-%d",autoVar,staticVar);
};
autoVar++;
staticVar++;
myBlock();

输出结果为

10-21
__main_block_impl_0
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int autoVar; //局部自动变量为值捕获
  int *staticVar; //局部静态变量为指针捕获
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _autoVar, int *_staticVar, int flags=0) : autoVar(_autoVar), staticVar(_staticVar) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • autoVar为值捕获,跟外部的autoVar失去了关联,所以外部的修改不影响Block内部捕获的值
  • staticVar为指针捕获,Block内部的指针仍然访问原来的内存空间
__main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int autoVar = __cself->autoVar; // bound by copy
  int *staticVar = __cself->staticVar; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_rd9sd5vj7_l6z9w7rpcd83g80000gn_T_main_a9a09f_mi_0,autoVar,(*staticVar));
 }

由此我们可以推测在Block是可以修改静态局部变量的值的

    int autoVar = 10;
    static int staticVar = 20;
    void (^myBlock)(void) = ^(){
        staticVar++;
    };
    autoVar++;
    myBlock();
    NSLog(@"%d-%d",autoVar,staticVar);

结果

11-21

__main_block_func_0实现为

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    int *staticVar = __cself->staticVar; // bound by copy
    (*staticVar)++; 
}

但是我们在Block内部修改局部自动变量是不行的

image.png

Variable is not assignable (missing __block type specifier)

提示应该用__block修改,我们后面会介绍__block

全局变量/静态全局变量/常量

int globalAutoVar = 10;
static int globalStaticVar = 20;
const int constLet = 30;
int main(int argc, char * argv[]) {
    void (^myBlock)(void) = ^(){
        NSLog(@"%d-%d-%d",globalAutoVar,globalStaticVar,constLet);
    };

    globalAutoVar++;
    globalStaticVar++;
    myBlock();
    return 0;
}

运行结果为

11-21-30
__main_block_impl_0
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

这里没有捕获任何值

__main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_rd9sd5vj7_l6z9w7rpcd83g80000gn_T_main_4b2a5a_mi_0,globalAutoVar,globalStaticVar,constLet);
 }

函数是直接访问的全局变量/全局静态变量/常量的值,外部的修改会直接作用于Block内部,同样可以在Block内部修改

int globalAutoVar = 10;
static int globalStaticVar = 20;
const int constLet = 30;

int main(int argc, char * argv[]) {
    void (^myBlock)(void) = ^(){
        globalAutoVar++;
        globalStaticVar++;
    };
    myBlock();
    NSLog(@"%d-%d-%d",globalAutoVar,globalStaticVar,constLet);
    return 0;
}

结果为

11-21-30

内存与生命周期分析

以上几种情况如果单看效果似乎不好理解,我们配合可执行文件加载到内存之后的数据分区以及数据的生命周期来分析加深理解

171d08939dd16f99_tplv-t2oaga2asx-watermark.webp

  • 局部变量autoVar存储在栈中,其生命周期和所处作用域同步。
  • 全局变量globalVar、全局静态变量globalStaticVar、局部静态变量staticVar都在全局区,其生命周期和进程同步
  • 常量constLet在常量区,不可修改,生命周期和进程生命周期同步

因为全局变量、全局静态变量、局部静态变量、常量的生命周期和进程同步,那么就不会出现Block的生命周期比所捕获的值生命周期更长的情况,也就不会出现野指针问题。

但是局部变量的生命周期和当前函数作用域有关系,出了作用域便会失效,为了防止出现野指针问题,这里采用值捕获;

OC对象类型捕获(引用类型)

JPerson *person = [JPerson new];
void(^myBlock)(void) = ^{
    NSLog(@"%@",person);
};

__main_block_impl_0

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  JPerson *person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, JPerson *_person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 对象类型person为指针捕获,注意这里有小坑

请问12输出内容一样吗??

    JPerson *person = [JPerson new];
    NSLog(@"1--%@",person);
    void (^myBlock)(void) = ^(){
        NSLog(@"2--%@",person);
    };
    person = [JPerson new];
    myBlock();

输出内容是一样的

1--<JPerson: 0x6000032dc0f0>
2--<JPerson: 0x6000032dc0f0>

区别于值类型引用类型本身就是一个指针,在Block内部捕获的也是一个指针,所以这里仍然是值传递,只不过传的值是一个指针

这种会报错

image.png

__main_block_desc_0

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
  • copy函数具体实现为__main_block_copy_0
  • dispose函数具体实现为__main_block_dispose_0

__main_block_copy_0

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) 
{
    _Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

__main_block_dispose_0

static void __main_block_dispose_0(struct __main_block_impl_0*src) 
{
    _Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

_Block_object_assign_Block_object_disposec函数,我们后面探索底层代码的时候会看其具体实现细节,这里插个眼

extern "C" __declspec(dllexport) void _Block_object_assign(void *, const void *, const int);
extern "C" __declspec(dllexport) void _Block_object_dispose(const void *, const int);

__block

上面我们在Block内部修改捕获的局部变量时报错,提示我们使用__block修饰,那么我们探索一下__block到底起到了什么作用

修饰基本数据类型

    __block int autoVar = 10;
    void(^myBlock)(void) = ^{
        autoVar = 20;
        NSLog(@"%d",autoVar);
    };
    myBlock();

运行结果为

20

查看底层代码

__attribute__((__blocks__(byref))) __Block_byref_autoVar_0 autoVar = {(void*)0,(__Block_byref_autoVar_0 *)&autoVar, 0, sizeof(__Block_byref_autoVar_0), 10};
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_autoVar_0 *)&autoVar, 570425344));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

删除类型修饰代码

__Block_byref_autoVar_0 autoVar = {
        (void*)0,
        (__Block_byref_autoVar_0 *)&autoVar,
        0,
        sizeof(__Block_byref_autoVar_0),
        10
    };
myBlock = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_autoVar_0 *)&autoVar, 570425344);
myBlock->FuncPtr((__block_impl *)myBlock);
__Block_byref_autoVar_0
struct __Block_byref_autoVar_0 {
  void *__isa;
__Block_byref_autoVar_0 *__forwarding;
 int __flags;
 int __size;
 int autoVar;
};

int main(int argc, const char * argv[]) {
......
    __Block_byref_autoVar_0 autoVar = {
        (void*)0,
        (__Block_byref_autoVar_0 *)&autoVar,
        0,
        sizeof(__Block_byref_autoVar_0),
        10
    };
......
}

可以看到经过__block修饰之后的值被包装成了__Block_byref_autoVar_0结构体

  • __forwarding指针指向了自己
  • int autoVar存储了值10
__main_block_impl_0
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_autoVar_0 *autoVar; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_autoVar_0 *_autoVar, int flags=0) : autoVar(_autoVar->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

int main(int argc, const char * argv[]) {
......
    __Block_byref_autoVar_0 autoVar = {
        (void*)0,
        (__Block_byref_autoVar_0 *)&autoVar,
        0,
        sizeof(__Block_byref_autoVar_0),
        10
    };

    myBlock = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_autoVar_0 *)&autoVar, 570425344);
......
}
  • __main_block_impl_0初始化时传入的就是包装之后的__Block_byref_autoVar_0类型的结构体指针
  • Flag也不在用默认值0,而是传入了一个值,这里后面源码时分析
__main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
{
    __Block_byref_autoVar_0 *autoVar = __cself->autoVar; // bound by ref
    (autoVar->__forwarding->autoVar) = 20;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_kx_rd9sd5vj7_l6z9w7rpcd83g80000gn_T_main_8ae80a_mi_0,(autoVar->__forwarding->autoVar));

}
  • __cself->autoVar先找到结构体指针autoVar
  • autoVar->__forwarding->autoVar结构体指针autoVar访问__forwarding指针,这里__forwarding仍然指向结构体autoVar,再寻找int autoVar赋值20
__main_block_desc_0
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

这里同样有copydispose函数,我们在后面源码中看其具体实现细节

__main_block_copy_0
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) 
{
    _Block_object_assign((void*)&dst->autoVar, (void*)src->autoVar, 8/*BLOCK_FIELD_IS_BYREF*/);
}
  • 和上面未经__block修饰时相比最后一个参数3变成了8
__main_block_dispose_0
static void __main_block_dispose_0(struct __main_block_impl_0*src) 
{
    _Block_object_dispose((void*)src->autoVar, 8/*BLOCK_FIELD_IS_BYREF*/);
}
  • 和上面未经__block修饰时相比最后一个参数3变成了8

image.png

修饰引用类型(OC对象类型)

    __block JPerson *person = [JPerson new];
    NSLog(@"1--%@",person);
    void(^myBlock)(void) = ^{
        person = [JPerson new];
        NSLog(@"2--%@",person);
    };
    myBlock();

查看底层代码

__attribute__((__blocks__(byref))) __Block_byref_person_0 person = {(void*)0,(__Block_byref_person_0 *)&person, 33554432, sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((JPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("JPerson"), sel_registerName("new"))};

void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));

((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

阅读起来依然那么不友好,删除类型修饰代码

__Block_byref_person_0 person = {
        (void*)0,
        (__Block_byref_person_0 *)&person,
        33554432,
        sizeof(__Block_byref_person_0),
        __Block_byref_id_object_copy_131,
        __Block_byref_id_object_dispose_131,
        ((JPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("JPerson"), sel_registerName("new"))
    };

myBlock = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344);

myBlock->FuncPtr((__block_impl *)myBlock);
__Block_byref_person_0
struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 JPerson *person;
};


int main(int argc, const char * argv[]) {
......
    __Block_byref_person_0 person = {
        (void*)0,
        (__Block_byref_person_0 *)&person,
        33554432,
        sizeof(__Block_byref_person_0),
        __Block_byref_id_object_copy_131,
        __Block_byref_id_object_dispose_131,
        ((JPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("JPerson"), sel_registerName("new"))
    };
......
}

相比于__block修饰基本数据类型这里多了两个方法声明,且Flag值也不再是0

  • __Block_byref_id_object_copy其具体实现为__Block_byref_id_object_copy_131
  • __Block_byref_id_object_dispose其具体实现为__Block_byref_id_object_dispose_131
  • Flag被赋值为33554432,后面源码分析其具体含义
__Block_byref_id_object_copy_131
static void __Block_byref_id_object_copy_131(void *dst, void *src) 
{
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
__Block_byref_id_object_dispose_131
static void __Block_byref_id_object_dispose_131(void *src) 
{
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

依然是熟悉的_Block_object_assign_Block_object_dispose

Block的存储域

Block根据存储域分为三种NSGlobalBlockNSStackBlockNSMallocBlock

NSGlobalBlock

  • 位于全局区
  • Block内部不使用局部自动变量
int globalVar = 10;
int main(int argc, const char * argv[]) {

    void(^blk1)(void) = ^{};
    void(^blk2)(void) = ^{
        NSLog(@"%d",globalVar);
    };

    void(^blk3)(void) = ^{
        NSLog(@"%@",@"abc");
    };
    
    const int constLet = 20;
    void(^blk4)(void) = ^{
        NSLog(@"%d",constLet);
    };
    
    static int staticVar = 20;
    void(^blk5)(void) = ^{
        NSLog(@"%d",staticVar);
    };
    
    NSLog(@"blk1 == %@",blk1);
    NSLog(@"blk2 == %@",blk2);
    NSLog(@"blk3 == %@",blk3);
    NSLog(@"blk4 == %@",blk4);
    NSLog(@"blk5 == %@",blk5);
    return 0;
}

运行结果

blk1 == <__NSGlobalBlock__: 0x104c8c208>
blk2 == <__NSGlobalBlock__: 0x104c8c228>
blk3 == <__NSGlobalBlock__: 0x104c8c248>
blk4 == <__NSGlobalBlock__: 0x104c8c268>
blk5 == <__NSGlobalBlock__: 0x104c8c288>

以下几种情况都属于NSGlobalBlock

  • 不使用任何外部变量
  • 使用全局变量、全局静态变量
  • 使用局部静态变量
  • 使用常量

NSStackBlock

  • 位于栈区
  • 可以在内部使用局部变量,但是不能赋值给强引用或者Copy修饰的变量
    int autoVar = 20;
    void(^__weak blk1)(void) = ^{
        NSLog(@"%d",autoVar);
    };
    NSLog(@"blk1 == %@",blk1);

运行结果

blk1 == <__NSStackBlock__: 0x16bc3b828>
  • Block使用了局部变量并且赋值给__weak指针

配置在栈上的Block会随着所属的变量作用域的结束而被废弃,如果Block当中有__block变量,该变量同样也会随着作用域的结束而被废弃。这就是__NSMallocBlock__的由来,Blocks提供了将Block__block变量从栈上复制到堆上的方法来解决这个问题。这样即使栈上Block的变量作用域结束,堆上的Block还可以继续存在。此时Blockisa将由_NSConcreteStackBlock变为_NSConcreteMallocBlock

出自# iOS-Block底层探索

NSMallocBlock

  • 位于堆区
  • Block内部使用局部变量并且赋值给强引用指针或者Copy修饰的变量
    int autoVar = 20;
    void(^ blk1)(void) = ^{
        NSLog(@"%d",autoVar);
    };

    void(^__weak blk2)(void) = ^{
        NSLog(@"%d",autoVar);
    };

    NSLog(@"blk1 == %@",blk1);
    NSLog(@"blk2 == %@",blk2);
    NSLog(@"blk3 == %@",[blk2 copy]);

运行结果

blk1 == <__NSMallocBlock__: 0x280db4360>
blk2 == <__NSStackBlock__: 0x16d2b7808>
blk3 == <__NSMallocBlock__: 0x280db4810>

那么什么情况下会被复制到堆上呢

typedef void(^blk_t)(void); 
int main(int argc, const char * argv[]) { 
    @autoreleasepool { 
        __block int a = 10; 
        NSLog(@"a的地址为%p", &a); 
        ^{ 
            a = 1; 
            NSLog(@"stack-a的地址为%p", &a); 
        }(); 
        blk_t blk = ^{ 
            a = 1; 
            NSLog(@"heap-a的地址为%p", &a); 
        }; 
        blk(); 
        NSLog(@"a的地址为%p", &a); 
    } 
    return 0; 
}

a的地址为0x7ffee3566c98
stack-a的地址为0x7ffee3566c98
heap-a的地址为0x6000012200b8
a的地址为0x6000012200b8

image.png

第三次和第四次打印的时候,__Block_byref_a_0结构体a已经被复制到了堆上,因此打印出来的的是堆上的地址。此时,栈上的结构体a可以通过__forwarding指针访问到堆上的a,即使栈上的变量废弃了,堆中的变量依然存在,可以正常访问。

那么什么情况下,栈上的block会被复制到堆上呢?大多数情况下编译器会适当的进行判断,自动生成将Block从栈上复制到堆上的代码。

  • 调用Blockcopy方法时
  • Block作为函数返回值返回时
  • Block赋值给__strong修饰符id类型的变量或者Block类型的成员变量
  • 在方法名中含有usingBlockCocoa框架方法或者Grand central DispatchAPI中传递Block时。

在看一个小例子

typedef void(^blk_t)(id obj);

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        blk_t blk;
        {
            id array = [[NSMutableArray alloc]init];
            blk = ^(id obj) {
                [array addObject:obj];
                NSLog(@"array count = %ld", [array count]);
            };
        }
        blk([[NSObject alloc]init]);
        blk([[NSObject alloc]init]);
        blk([[NSObject alloc]init]);
    }
    return 0;
}

输出结果是什么???

array count = 1
array count = 2
array count = 3

这是一个堆Block所以即使出了作用域blk仍然能访问堆上的Block实例对象

image.png

block循环引用问题

    @property(nonatomic,strong) id myBlock;
    ...
    self.myBlock = ^(void){
        NSLog(@"%@", self);
    };
    self.myBlock();

self持有Block,同时在block中也捕获了self,相互持有谁都不放手导致内存泄漏,那么解决block循环引用有哪些方法呢?

__weak & __strong强弱指针

   __weak typeof(self) weakSelf = self;
    self.myBlock = ^(void){
       // 为什么这里需要用强指针指向呢?为了防止捕获的外部变量释放以后这里出现野指针
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%@",strongSelf);
    };

__block

    __block UIViewController * blockSelf = self;
    self.myBlock = ^(void){
        NSLog(@"%@",blockSelf);
        blockSelf = nil;
    };

blockSelf被封装成了block_byref结构体,结构体中持有self的引用, block内部捕获了结构体的指针,同时self持有block,这是一个三方引用,需要我们手动打破这种局面,所以在block中需要手动将blockSelf置为nil

image.png

self作为参数传入block

typedef void(^ Blk)(BlockVC *);
@interface BlockVC ()
@property(nonatomic,strong) Blk myBlock;
@end
...
    self.myBlock = ^(BlockVC * paraSelf){
        NSLog(@"%@",paraSelf);
    };
    self.myBlock(self);

传入的参数是放在函数的栈空间,由系统控制释放,不会造成循环引用问题

Block源码分析

下面看一下Block源码,这里用的版本是libclosure-78

首先看一下Block_private.h文件

Block_layout

struct Block_layout {
    void * __ptrauth_objc_isa_pointer isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

这里有一个flags我们看一下

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime 释放
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime 引用计数掩码
    BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler 
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
    BLOCK_SMALL_DESCRIPTOR =  (1 << 22), // compiler
#endif
    BLOCK_IS_NOESCAPE =       (1 << 23), // compiler
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime 堆block
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler 有copy/dispose方法
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code 有c++析构函数
    BLOCK_IS_GC =             (1 << 27), // runtime 
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler 全局block
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler 
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31// compiler
};

虽然Block_layout里面只有一个Block_descriptor_1,但是实际情况根据flags还可能有Block_descriptor_2Block_descriptor_3,2和3是通过1的内存地址偏移访问的

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved; //保留字端
    uintptr_t size; //block大小
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy; 
    BlockDisposeFunction dispose;
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

Block_byref结构体

__block修饰的变量被捕获以后是被封装成了Block_byref结构体,根据flags判断还可能有Block_byref_2Block_byref_3,同样通过内存偏移访问

struct Block_byref {
    void * __ptrauth_objc_isa_pointer isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};

flags枚举值

// Values for Block_byref->flags to describe __block variables
enum {
    // Byref refcount must use the same bits as Block_layout's refcount. 
    // 低16位和Block_layout->flags保持一致
    // BLOCK_DEALLOCATING =      (0x0001),  // runtime
    // BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_BYREF_LAYOUT_MASK =       (0xf << 28), // compiler
    BLOCK_BYREF_LAYOUT_EXTENDED =   (  1 << 28), // compiler
    BLOCK_BYREF_LAYOUT_NON_OBJECT = (  2 << 28), // compiler
    BLOCK_BYREF_LAYOUT_STRONG =     (  3 << 28), // compiler strong类型
    BLOCK_BYREF_LAYOUT_WEAK =       (  4 << 28), // compiler weak类型
    BLOCK_BYREF_LAYOUT_UNRETAINED = (  5 << 28), // compiler
    BLOCK_BYREF_IS_GC =             (  1 << 27), // runtime
    BLOCK_BYREF_HAS_COPY_DISPOSE =  (  1 << 25), // compiler 有copy/dispose函数
    BLOCK_BYREF_NEEDS_FREE =        (  1 << 24), // runtime 堆
};

接下来从runtime.cpp文件看几个方法的实现细节

_Block_copy

// Copy, or bump refcount, of a block.  If really copying, call the 
copy helper if present.
//栈block拷贝到堆上,堆上的block引用计数+1,还会触发copy辅助函数的调用
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;
    
    if (!arg) return NULL;
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    
    //堆block
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    
    //全局block
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    
    //栈block
    else {
        // Its a stack block.  Make a copy.
        size_t size = Block_size(aBlock);
        
        //在内存开辟大小为size的空间
        struct Block_layout *result = (struct Block_layout *)malloc(size);
        if (!result) return NULL;
        
        //将栈上的block拷贝到堆上
        memmove(result, aBlock, size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        
        //函数指针
        result->invoke = aBlock->invoke;
#if __has_feature(ptrauth_signed_block_descriptors)
        if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
            uintptr_t oldDesc = ptrauth_blend_discriminator(
                    &aBlock->descriptor,
                    _Block_descriptor_ptrauth_discriminator);
            uintptr_t newDesc = ptrauth_blend_discriminator(
                    &result->descriptor,
                    _Block_descriptor_ptrauth_discriminator);
            result->descriptor =
                    ptrauth_auth_and_resign(aBlock->descriptor,
                                            ptrauth_key_asda, oldDesc,
                                            ptrauth_key_asda, newDesc);
        }
#endif
#endif
        // reset refcount
        // BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING结果为低16位全为1,取反之后全为0
        // result->flags &= 之后低16位清零
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        
        // 将BLOCK_NEEDS_FREE位置为1,将引用计数置为1
        // 最低位为BLOCK_DEALLOCATING标志位,引用计数从第二位开始
        // 所以是2,也就是2进制的 10
        // 这样设计的巧妙之处在于引用计数为1时如果引用计数再做减1操作,
        // 那么引用计数变为0,BLOCK_DEALLOCATING标志位变为1
        result->flags |= BLOCK_NEEDS_FREE | 2// logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        // isa赋值为mallocBlock也就是堆block
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

latching_incr_int

static int32_t latching_incr_int(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        
        //如果引用计数已经达到最大,不做处理,现实中几乎不会出现这种情况
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return BLOCK_REFCOUNT_MASK;
        }
        
        //引用计数+1
        if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
            return old_value+2;
        }
    }
}

_Block_call_copy_helper

static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
    if (auto *pFn = _Block_get_copy_function(aBlock))
        pFn(result, aBlock);
}

_Block_get_copy_function

static inline __typeof__(void (*)(void *, const void *))
_Block_get_copy_function(struct Block_layout *aBlock)
{
    if (!(aBlock->flags & BLOCK_HAS_COPY_DISPOSE))
        return NULL;
        
        //获取block的Block_descriptor_1
    void *desc = _Block_get_descriptor(aBlock);
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
    if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
        struct Block_descriptor_small *bds =
                (struct Block_descriptor_small *)desc;
        return _Block_get_relative_function_pointer(
                bds->copy, void (*)(void *, const void *));
    }
#endif

// 通过内存偏移获取Block_descriptor_2,这里存储了copy/dispose辅助函数
    struct Block_descriptor_2 *bd2 =
            (struct Block_descriptor_2 *)((unsigned char *)desc + sizeof(struct Block_descriptor_1));
    return _Block_get_copy_fn(bd2);
}

_Block_get_copy_fn

static inline __typeof__(void (*)(void *, const void *))
_Block_get_copy_fn(struct Block_descriptor_2 *desc)
{
//返回的是Block_descriptor_2中的copy函数
    return (void (*)(void *, const void *))_Block_get_function_pointer(desc->copy);
}

进行到这里可以知道_Block_call_copy_helper最终执行了copy方法,结合前面我们通过xcrun查看底层实现可以知道copy函数的实现中调用了_Block_object_assign

_Block_object_assign

//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// 用来处理Blocks和Block_byrefs捕获对象需要拷贝到堆上的情况
// to do the assignment.
//
void _Block_object_assign(void *destArg, const void *object, const int flags) {
    // destArg是捕获对象的指针的地址
    // object是捕获对象的指针
    // flag是标志位
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
    
       //捕获的是OC对象类型
      case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/
        
        //追踪进去发现什么都没有做
        _Block_retain_object(object);
        //捕获对象的引用计数+1
        *dest = object;
        break;
        
        //捕获的是Block
      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/
        *dest = _Block_copy(object);
        break;
        
        //捕获的是__block或者__weak __block修饰的变量
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/
        *dest = _Block_byref_copy(object);
        break;
        
        // Block_byref结构体拷贝到堆上时
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/
        *dest = object;
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/
        *dest = object;
        break;
      default:
        break;
    }
}

_Block_object_assign函数负责的工作是Block从栈上拷贝到堆上时所捕获的变量的拷贝过程,主要处理了以下几种类型

  • OC对象类型
  • Block类型
  • __block修饰的变量的拷贝,会触发Block_byref结构体的拷贝
  • Block_byref结构体的拷贝

_Block_byref_copy

// A closure has been copied and its fixup routine is asking us to fix up the reference to the shared byref data
// Block拷贝之后捕获的Block_byref结构体也需要拷贝

// Closures that aren't copied must still work, so everyone always accesses variables after dereferencing the forwarding ptr.
//栈上的变量也必须能正常工作,栈上的Block_byref结构体通过forwarding指向堆上的
Block_byref结构体

// We ask if the byref pointer that we know about has already been copied to the heap, and if so, increment and return it.
// 如果已经被拷贝了,那么引用计数+1并返回堆上的结构体地址

// Otherwise we need to copy it and update the stack forwarding pointer 
// 否则我们需要将其拷贝到堆上并修改栈上结构体的forwarding指针
static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;
    
    //引用计数已经为0,说明在栈上
    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack 在堆开辟空间
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        
        // 不是一个对象类型,不需要isa
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        // 引用计数为2,分别为
        // 栈上byref的forwarding指针访问堆上byref结构体
        // 堆上的block指针通过指针访问堆上byref结构体
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        
        // 栈上的forwarding指针指向堆上,堆上的forwarding指针指向自己
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
        
        //如果有copy/dispose辅助函数,通过内存偏移访问Block_byref_2和Block_byref_3
        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;
            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }
            
            //执行Block_byref_2中的copy函数
            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
    
    //如果已经在堆上了,那么引用计数+1
        latching_incr_int(&src->forwarding->flags);
    }
    return src->forwarding;
}

latching_incr_int

static int32_t latching_incr_int(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        // 引用计数已经最大了
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return BLOCK_REFCOUNT_MASK;
        }
        // 引用计数+1
        if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
            return old_value+2;
        }
    }
}

再看一下释放的方法

_Block_release

// API entry point to release a copied Block
// 已经拷贝到堆上的block需要通过_Block_release释放
void _Block_release(const void *arg) {
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;
    
    // 全局block不需要处理
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;
    
    //不是堆上的block,不需要处理
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
    
    // 堆上的block
    if (latching_decr_int_should_deallocate(&aBlock->flags)) {
        _Block_call_dispose_helper(aBlock);
        _Block_destructInstance(aBlock);
        
        //释放内存
        free(aBlock);
    }
}

latching_decr_int_should_deallocate

// return should_deallocate?
static bool latching_decr_int_should_deallocate(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        
        //引用计数最大不处理
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return false; // latched high
        }
        
        //引用计数为0不处理
        if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
            return false;   // underflow, latch low
        }
        
        
        int32_t new_value = old_value - 2;
        bool result = false;
        
        //如果引用计数为1,引用计数-1,引用计数为0 BLOCK_DEALLOCATING为1,返回true
        if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) {
            new_value = old_value - 1;
            result = true;
        }
        if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) {
            return result;
        }
    }
}

_Block_call_dispose_helper

static void _Block_call_dispose_helper(struct Block_layout *aBlock)
{
    if (auto *pFn = _Block_get_dispose_function(aBlock))
        pFn(aBlock);
}

_Block_call_dispose_helper最终调用的是_Block_object_dispose

_Block_object_dispose

// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point Blocks或者Block_byrefs捕获对象需要释放的时候走这个方法
// to help dispose of the contents
void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
    //如果捕获的是__block对象,Block_byrefs需要被释放
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);
        break;
        
        //如果捕获的是Block对象
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
        
        //如果捕获的是OC对象
      case BLOCK_FIELD_IS_OBJECT:
        _Block_release_object(object);
        break;
        
        // 这里要处理Block_byrefs内部持有的数据的释放,可以看到实现为空,
        // 因为Block_byrefs释放的时候内部持有的变量也就释放了
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        break;
      default:
        break;
    }
}

_Block_byref_release

static void _Block_byref_release(const void *arg) {
    struct Block_byref *byref = (struct Block_byref *)arg;
    // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
    byref = byref->forwarding;
    
    //在堆上,才需要释放
    if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
        int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
        os_assert(refcount);
        
        //如果引用计数-1之后为0
        if (latching_decr_int_should_deallocate(&byref->flags)) {
        
            //如果有dispose辅助函数
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
            
                // 执行dispose函数
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                (*byref2->byref_destroy)(byref);
            }
            
            //内存释放
            free(byref);
        }
    }
}

_Block_release_object

_Block_release_object实现为空,什么都没干,因为无论是Blocks捕获了对象还是block_byref捕获了对象,一旦持有者释放之后,那么对象也会被释放了

此处分析完毕以后我们对Block有了一个整体的认识,但是还有一些细节需要深入理解,比如__weak __block__block的区别,这些内容需要在后续研究完__weak相关内容之后再回顾!!!

引用计数练习题

AB输出各为多少????

typedef void(^blk_t)(void);
int main(int argc, char * argv[]) {

@autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
        NSLog(@"A---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
        blk_t blk = ^{
            obj;
        };
        NSLog(@"B---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
    }
    return 0;
}

我们先来分析一下这道题

  • 栈上有一个指针obj指向堆上的对象,引用计数为1
  • blk为堆blockblk中值捕获了obj,此时引用计数为2 所以最终结果应该是:A--1B--2

image.png

我们看一下真实的结果是多少

A---1
B---3

为什么B3呢???因为堆blk是从拷贝过去的,而此时栈blk还没有释放

image.png 假如我们将这个题目修改一下

typedef void(^blk_t)(void);
int main(int argc, char * argv[]) {

@autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
        NSLog(@"A---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
        blk_t blk;
        {
            blk = ^{
                obj;
            };
        }
        blk();
        NSLog(@"B---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
    }
    return 0;
}

在打印B的时候栈blk已经出了作用域,释放了 image.png 所以打印结果为

A---1
B---2

我们再修改一下题目

typedef void(^blk_t)(void);
int main(int argc, char * argv[]) {

@autoreleasepool {
        blk_t blk;
        {
            NSObject *obj = [[NSObject alloc] init];
            NSLog(@"A---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
            {
                blk = ^{
                    NSLog(@"B---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
                };
            }
        }
        blk();
    }
    return 0;
}

当执行blk();的时候栈上的obj栈blk都出了作用域释放掉了,只剩下堆blk里面的obj指针可以访问实例变量,此时引用计数为1

A---1
B---1

如果我们将前面三种情况的obj都用__block修饰,那么执行结果是什么

typedef void(^blk_t)(void);
int main(int argc, char * argv[]) {

@autoreleasepool {
        blk_t blk;
        {
            __block NSObject *obj = [[NSObject alloc] init];
            NSLog(@"A---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
            {
                blk = ^{
                    NSLog(@"B---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
                };
            }
        }
        blk();
    }
    return 0;
}

此时栈上的指针objbyref结构体持有,但是栈上的byref和栈上的blk都释放了,只剩下堆上的blk通过指针访问堆上的byref,进而通过__forwarding指针访问到obj,引用计数为1

A---1
B---1

修改一下

typedef void (^blk_t)(void);
int main(int argc, char * argv[]) {

    @autoreleasepool {
        __block NSObject *obj = [[NSObject alloc] init];
        NSLog(@"A---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
        blk_t blk;
        {
            blk = ^{
                obj;
            };
        }
        blk();
        NSLog(@"B---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
    }
    return 0;
}

和不加__block的时候不太一样,执行结果为

A---1
B---1

此时虽然栈上的byref结构体还没释放,但是栈byref__forwarding指针指向了堆byref堆byref通过__forwarding指针访问到obj,所以真正指向实例变量的只有一个堆byref结构体里面的obj指针,引用计数为1

image.png

再修改一下

typedef void (^blk_t)(void);

int main(int argc, char * argv[]) {

    @autoreleasepool {
        __block NSObject *obj = [[NSObject alloc] init];
        NSLog(@"A---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
        blk_t blk = ^{
            obj;
        };
        blk();
        NSLog(@"B---%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));
    }
    return 0;
}

此时也和不加__block不同

A---1
B---1

image.png

此时无论是栈blk还是栈byref结构体还是堆blk,最终都是通过堆byref中的obj指针访问到了实例变量,所以引用计数为1

参考文章

# iOS-Block底层探索