刨根问底block

刨根问底block

[toc]

1.block本质

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

1.1 block数据结构

在oc中的实现

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};
image.png

验证

补充

  • 将Objective-C代码转换为C\C++代码
  • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
  • 如果需要链接其他框架,使用-framework参数。比如-framework UIKit

原理的oc代码

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int temp = 10;
        void(^block)(int,int) = ^(int a ,int b){
            NSLog(@"这是一个block,---%d",temp);
        };
        temp = 20;
        block(10,10);
    }
    return 0;
}

执行下面脚本,将oc代码转换成C++代码

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

如果遇到__weak运行时内容,则执行

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

转换为main.cpp后对应关系如下

 int temp = 10;
  void(^block)(int,int) = ^(int a ,int b){
            NSLog(@"这是一个block,---%d",temp);
        };
   temp = 20;      
block(10,10);

分别对应
转化后的c++代码如下:我们简称代码A(下面用到)

 int temp = 10;
//定义block变量    
void(*block)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, temp));
 //执行bloc代码    
 temp = 20;     
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);

__main_block_impl_0 的定义为

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

结构体'__block_impl'定义又为

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

所以__main_block_impl_0可以为

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

看到这个结构跟Block_layout类似,另外temp作为一个block变量

1.2 理解block数据结构以及调用

代码A里面都是一些强制转换,我们去掉强制转换,结构为

// 定义block变量
       void(*block)(int,int) = &__main_block_impl_0(__main_block_func_0, 
       &__main_block_desc_0_DATA, temp);

        // 执行block内部的代码
        block->FuncPtr(block,10,10);
  • block__main_block_impl_0函数执行结果,并把地址赋给
    block
  • __main_block_impl_0__main_block_impl_0结构体的构造函数

所以block指针是一个指向结构体对象,也就是block是结构体对象

1、__main_block_func_0的代码为

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  int temp = __cself->temp; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_c0a19f_mi_0,temp);
        }
image.png

通过代码可以知道,把block内部执行逻辑赋给了__block_implFuncPtr
2、__main_block_desc_0_DATA

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)};

通过sizeof(struct __main_block_impl_0) 可知道这个参数主要就是对block大小的描述

总结:

  1. 定义block时,将block内部执行函数封装到__main_block_func_0
  2. 调用__main_block_impl_0结构体的构造函数,并把地址赋给block,
  3. __main_block_impl_0有两个主要成员implDesc
  4. __main_block_func_0 作为第一个参数传入__main_block_impl_0__main_block_impl_0的size等信息作为第二个参数
  5. __main_block_func_0赋给了implFuncPtr
  6. 调用block时,就是FuncPtr,并把block作为参数传进去

2.变量捕获

变量类型:

  1. 自动变量:离开作用域就销毁的局部变量,就是auto变量
  2. static变量:局部变量内部使用static变量的变量,为static变量

2.1 auto变量捕获

代码A的局部变量 temp就是一个auto变量,因为C语言会默认给这种变量增加一个auto关键字,auto是默认关键字,

自动变量:离开作用域就销毁的变量,就是auto变量

代码A的打印结果为:

2020-10-25 08:55:59.897766+0800 Blocktest[7366:84510] 这是一个block,---10
Program ended with exit code: 0

结构是10 而不是20
原因:
如下代码,在定义block的时候,已经把temp作为参数传递给了结构体,结构体__main_block_impl_0里有一个变量temp,存储了外部的temp,也就是捕获了变量temp


        void(*block)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, temp));
     
        
        
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int temp;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _temp, int flags=0) : temp(_temp) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

执行函数体的地方,打印的是__main_block_impl_0内部的temp变量,所以没变

//定义block内部执行逻辑
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  int temp = __cself->temp; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_c0a19f_mi_0,temp);
        }

实际上这也就是auto变量的捕获,可以发现此时的变量是值传递的方式

2.2 static变量

修改代码为

  int temp = 10;
        static int  v = 10;
        void(^block)(int,int) = ^(int a ,int b){
            NSLog(@"这是一个block,---%d",temp);
            NSLog(@"这是一个block,---%d",temp1);
        };
        temp = 20;
        temp1 = 20;
        block(10,10);

打印结果是

2020-10-22 21:11:19.398797+0800 Blocktest[1739:21133] 这是一个block,---10
2020-10-22 21:11:19.399428+0800 Blocktest[1739:21133] 这是一个block,---20
Program ended with exit code: 0

clang查看转换后的c++源码为

//__main_block_impl_0部分
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int temp;
  int *temp1;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _temp, int *_temp1, int flags=0) : temp(_temp), temp1(_temp1) {
    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, int a, int b) {
  int temp = __cself->temp; // bound by copy
  int *temp1 = __cself->temp1; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_e1517b_mi_0,temp);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_e1517b_mi_1,(*temp1));
        }
        
        //main部分
 int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int temp = 10;
        static int temp1 = 10;
        void(*block)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, temp, &temp1));
        temp = 20;
        temp1 = 20;
        ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
    }
    return 0;
}       

发现可以知道静态变量,将temp1的地址值赋值给__main_block_impl_0temp1指针,所以temp1修改为20时,打印的时候打的是temp1指针指向的地址数据,所以打印20

结论:static是指针传递捕获变量

延伸:为什么auto是值传递,static是指针传递
猜测:

  • auto变量:在一个代码块里执行结束,会被销毁,所以在block里捕获没必要指针,因为被销毁,所以再次访问会造成野指针
  • static变量:一直保存在内存中,代码块结束后,静态变量指针还在

2.3 全局变量

修改代码为


int temp_ = 10;
static int temp1_ = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int temp = 10;
        static int temp1 = 10;
        void(^block)(int,int) = ^(int a ,int b){
            NSLog(@"这是一个block,---%d",temp);
            NSLog(@"这是一个block,---%d",temp1);
            NSLog(@"这是一个block,---%d",temp_);
            NSLog(@"这是一个block,---%d",temp1_);
        };
        temp = 20;
        temp1 = 20;
        temp_ = 20;
        temp1_ = 20;
        block(10,10);
    }
    return 0;
}

执行结果是

2020-11-02 21:27:48.500471+0800 Blocktest[2911:37937] 这是一个block,---10
2020-11-02 21:27:48.501084+0800 Blocktest[2911:37937] 这是一个block,---20
2020-11-02 21:27:48.501163+0800 Blocktest[2911:37937] 这是一个block,---20
2020-11-02 21:27:48.501201+0800 Blocktest[2911:37937] 这是一个block,---20
Program ended with exit code: 0

clang查看c++代码


int temp_ = 10;
static int temp1_ = 10;


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int temp;
  int *temp1;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _temp, int *_temp1, int flags=0) : temp(_temp), temp1(_temp1) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  int temp = __cself->temp; // bound by copy
  int *temp1 = __cself->temp1; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_b62fb6_mi_0,temp);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_b62fb6_mi_1,(*temp1));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_b62fb6_mi_2,temp_);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_b62fb6_mi_3,temp1_);
        }

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)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int temp = 10;
        static int temp1 = 10;
        void(*block)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, temp, &temp1));
        temp = 20;
        temp1 = 20;
        temp_ = 20;
        temp1_ = 20;
        ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
    }
    return 0;
}

可以看到全局变量是直接访问,并没有捕获到block里面,所以修改后,值都变成是20

局部变量需要捕获,全局变量不需要捕获原因:

  • 因为全局变量一直在内存中,可以一直访问,所以没必要捕获
  • 局部需要捕获原因:还是代码块作用域的原因,因为在代码块作用域后,不能再次访问作用域里的局部变量。而block执行相当于在另一个作用域函数执行访问变量,所以需要捕获到block里面

2.4 对象捕获

创建student类,代码为:

@implementation Student
- (void)test{
    void(^bloc)(void) = ^{
        NSLog(@"stutend ====%@",self);
        NSLog(@"stutend ====%@",self->_name);
    };
    bloc();
}

- (instancetype)initWithName:(NSString *)name
{
    if (self = [super init]) {
        self.name = name;
    }
    return self;
}
@end 

clang查看c++代码为


struct __Student__test_block_impl_0 {
  struct __block_impl impl;
  struct __Student__test_block_desc_0* Desc;
  Student *self;
  __Student__test_block_impl_0(void *fp, struct __Student__test_block_desc_0 *desc, Student *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __Student__test_block_func_0(struct __Student__test_block_impl_0 *__cself) {
  Student *self = __cself->self; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_Student_05c7f3_mi_0,self);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_Student_05c7f3_mi_1,(*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Student$_name)));
    }
static void __Student__test_block_copy_0(struct __Student__test_block_impl_0*dst, struct __Student__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __Student__test_block_dispose_0(struct __Student__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __Student__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __Student__test_block_impl_0*, struct __Student__test_block_impl_0*);
  void (*dispose)(struct __Student__test_block_impl_0*);
} __Student__test_block_desc_0_DATA = { 0, sizeof(struct __Student__test_block_impl_0), __Student__test_block_copy_0, __Student__test_block_dispose_0};

static void _I_Student_test(Student * self, SEL _cmd) {
    void(*bloc)(void) = ((void (*)())&__Student__test_block_impl_0((void *)__Student__test_block_func_0, &__Student__test_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)bloc)->FuncPtr)((__block_impl *)bloc);
}


static instancetype _Nonnull _I_Student_initWithName_(Student * self, SEL _cmd, NSString * _Nonnull name) {
    if (self = ((Student *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("init"))) {
        ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString * _Nonnull)name);
    }
    return self;
}

static NSString * _Nonnull _I_Student_name(Student * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Student$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_Student_setName_(Student * self, SEL _cmd, NSString * _Nonnull name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Student, _name), (id)name, 0, 1); }

可以捕获到

因为oc函数转换成C++时,都会默认添加两个参数(Student * self, SEL _cmd),参数分别为方法调用者,方法名。参数也是局部变量。

如何捕获

把self指针传递捕获到__Student__test_block_impl_0Student *self指针

2.5 总结

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

变量类型 捕获到block内部 访问方式
auto 捕获 值传递
static 捕获 指针传递
全局变量 不捕获 直接访问

局部变量需要捕获,全局变量不需要捕获原因:

  • 因为全局变量一直在内存中,可以一直访问,所以没必要捕获
  • 局部需要捕获原因:还是代码块作用域的原因,因为在代码块作用域后,不能再次访问作用域里的局部变量。而block执行相当于在另一个作用域函数执行访问变量,所以需要捕获到block里面

为什么auto是值传递,static是指针传递

  • auto变量:在一个代码块里执行结束,会被销毁,所以在block里捕获没必要指针,因为被销毁,所以再次访问会造成野指针
  • static变量:一直保存在内存中,代码块结束后,静态变量指针还在

3.block类型

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

  • __NSGlobalBlock__ ( _NSConcreteGlobalBlock )
  • __NSStackBlock__ ( _NSConcreteStackBlock )
  • __NSMallocBlock__ ( _NSConcreteMallocBlock )
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        void(^block)(int,int) = ^(int a ,int b){
         
        };
        
        NSLog(@"block类型==%@",[block class]);
        NSLog(@"block父类类型==%@",[[block class] superclass]);
        NSLog(@"block父类的父类类型==%@",[[[block class] superclass] superclass]);
        NSLog(@"block父类的父类的类型==%@",[[[[block class] superclass] superclass] superclass]);
    }
    return 0;
}

执行结果

2020-11-02 22:12:16.481399+0800 Blocktest[6752:90620] block类型==__NSGlobalBlock__
2020-11-02 22:12:16.481856+0800 Blocktest[6752:90620] block父类类型==__NSGlobalBlock
2020-11-02 22:12:16.481939+0800 Blocktest[6752:90620] block父类的父类类型==NSBlock
2020-11-02 22:12:16.481992+0800 Blocktest[6752:90620] block父类的父类的类型==NSObject
Program ended with exit code: 0

进一步验证block是oc对象

3.1执行三种类型

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block1)(void) = ^(){
            NSLog(@"block1");
        };
        int age = 10;
        void(^block2)(void) = ^(){
            NSLog(@"block2===%d",age);
        };
        
        NSLog(@"%@ %@ %@",[block1 class] ,[block2 class],[^{
            NSLog(@"%d",age);
                }class]);
    }
    return 0;
}

执行结果

2020-11-02 22:18:15.039022+0800 Blocktest[7396:97958] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
Program ended with exit code: 0
  • 但是clang查看c++源码如下,发现都是_NSConcreteStackBlock类型。
  • 这是因为oc是动态语言,一切以runtime运行时的结果为准
  • 编译完是下c++代码,但是运行时通过runtime运行时动态修改,所以不一样。

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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_8d9d27_mi_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)};

struct __main_block_impl_1 {
  struct __block_impl impl;
  struct __main_block_desc_1* Desc;
  int age;
  __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_1(struct __main_block_impl_1 *__cself) {
  int age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_8d9d27_mi_1,age);
        }

static struct __main_block_desc_1 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_1_DATA = { 0, sizeof(struct __main_block_impl_1)};

struct __main_block_impl_2 {
  struct __block_impl impl;
  struct __main_block_desc_2* Desc;
  int age;
  __main_block_impl_2(void *fp, struct __main_block_desc_2 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_2(struct __main_block_impl_2 *__cself) {
  int age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_8d9d27_mi_3,age);
                }

static struct __main_block_desc_2 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_2_DATA = { 0, sizeof(struct __main_block_impl_2)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void(*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        int age = 10;
        void(*block2)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, age));

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_8d9d27_mi_2,((Class (*)(id, SEL))(void *)objc_msgSend)((id)block1, sel_registerName("class")) ,((Class (*)(id, SEL))(void *)objc_msgSend)((id)block2, sel_registerName("class")),((Class (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__main_block_impl_2((void *)__main_block_func_2, &__main_block_desc_2_DATA, age)), sel_registerName("class")));
    }
    return 0;
}

3.2 block三种内存布局

iOS程序的内存布局如下

image.png
  • 代码段:编译之后的代码
  • 数据段
    • 字符串常量:比如NSString *str = @"123"
    • 已初始化数据:已初始化的全局变量、静态变量等
    • 未初始化数据:未初始化的全局变量、静态变量等
  • 栈:函数调用开销,比如局部变量。分配的内存空间地址越来越小
  • 堆:通过alloc、malloc、calloc等动态分配的空间,分配的内存空间地址越来越大

三种block的内存分配如下

image.png

3.3 block类型确定

block类型 产生原因
__NSGlobalBlock__ 没有访问auto变量
__NSStackBlock__ 访问了auto变量
__NSMallocBlock__ __NSStackBlock__调用了copy
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    //global类型,因为没有访问auto变量
        void(^block1)(void) = ^(){
            NSLog(@"block1");
        };
        int age = 10;
        //__NSMallocBlock__类型,因为访问auto变量,但是arc自动调用了copy
        void(^block2)(void) = ^(){
            NSLog(@"block2===%d",age);
        };
        //第三个class调用auto变量
        NSLog(@"%@ %@ %@",[block1 class] ,[block2 class],[^{
            NSLog(@"%d",age);
                }class]);
    }
    return 0;
}

执行结果

2020-11-02 22:28:15.039022+0800 Blocktest[7396:97958] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
Program ended with exit code: 0

__NSStackBlock__问题

超如作用域,函数栈自动销毁,当访问作用域变量,由于销毁会发生错乱

mrc 设置方法
Build Settings 搜索automatic reference如下图,改为NO即可
[图片上传失败...(image-eff3b4-1605196903148)]

image.png

mrc运行代码

void (^block)(void);
void test2()
{
    // NSStackBlock
    int age = 10;
    block = ^{
        NSLog(@"block---------%d", age);
    } ;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test2();
        block(); 
    }
    return 0;
}

运行结果,发现结果错乱

2020-11-02 23:06:51.888443+0800 Blocktest[11938:153696] block----------272632760
Program ended with exit code: 0

所以arc自动copy,把block放到堆上,把他变成__NSMallocBlock__类型,有程序员自己决定是否销毁,由于arc帮我们做了内存的管理,自动添加了release

总结

image.png

这也解释了开发中我们对block使用copy属性

4.block的copy

在arc环境下,编译器会根据情况自动将粘上的block复制到堆上,所属情况如下

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

4.1 block作为函数返回值时

编写代码
arc环境下

typedef void(^LYJBlock)(void);

LYJBlock lyjBlock(){
    int a = 10;
    return ^{
        NSLog(@"block内部==%d",a);
    };
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LYJBlock block = lyjBlock();
        
        NSLog(@"类型===%@",[block class]);
        block();
    }
    return 0;
}

打印正常

2020-11-05 22:05:04.972907+0800 Block-copy[85340:714362] 类型===__NSMallocBlock__
2020-11-05 22:05:04.973409+0800 Block-copy[85340:714362] block内部==10
Program ended with exit code: 0

这是因为编译器自动进行了copy操作

mrc环境可以看到编译报错

image.png

4.2 将block赋值给__strong指针时

修改代码

 int a = 10;
        LYJBlock block = ^{
            NSLog(@"block内部==%d",a);
        };
        
        NSLog(@"类型===%@",[block class]);
        block();

执行结果

2020-11-05 22:08:35.371983+0800 Block-copy[85872:720914] 类型===__NSMallocBlock__
2020-11-05 22:08:35.372516+0800 Block-copy[85872:720914] block内部==10
Program ended with exit code: 0

这是因为block对象被block强指针指着

mrc环境执行为

20-11-05 22:15:53.232727+0800 Block-copy[86746:734403] 类型===__NSStackBlock__
2020-11-05 22:15:53.233273+0800 Block-copy[86746:734403] block内部==10
Program ended with exit code: 0

4.3 block作为Cocoa API中方法名含有usingBlock的方法参数时

例如

  NSArray *array = @[];
        [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            
        }];

4.4 block作为GCD API的方法参数时

例如:

 static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            
        });
        

5.内存管理

5.1 auto变量捕获对象类型细节

修改代码为
arc环境下


typedef void(^LYJBlock)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
//        int temp = 10;
//        static int temp1 = 10;
//        void(^block)(int,int) = ^(int a ,int b){
//            NSLog(@"这是一个block,---%d",temp);
//            NSLog(@"这是一个block,---%d",temp1);
//            NSLog(@"这是一个block,---%d",temp_);
//            NSLog(@"这是一个block,---%d",temp1_);
//        };
//        temp = 20;
//        temp1 = 20;
//        temp_ = 20;
//        temp1_ = 20;
//        block(10,10);
        LYJBlock block ;
        {
            Student *stu = [Student new];
            stu.name = @"10";
            block = ^{
                NSLog(@"-----block内部==%@",stu.name);
            };
        }
        
        NSLog(@"---------");//断点行
    }
    return 0;
}

在打断点处发现student没有释放,原因
简化代码

  Student *stu = [Student new];
            stu.name = @"10";
        LYJBlock block = ^{
                NSLog(@"-----block内部==%@",stu.name);
            };
//        }
        
        NSLog(@"---------");

clang查看c++代码 ,这次加上arc 和runtime进行

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m



int temp_ = 10;
static int temp1_ = 10;

typedef void(*LYJBlock)(void);

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Student *__strong stu;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Student *__strong _stu, int flags=0) : stu(_stu) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  Student *__strong stu = __cself->stu; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_925dfe_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)stu, sel_registerName("name")));
            }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->stu, (void*)src->stu, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        LYJBlock block ;
        {
            Student *stu = ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"));
            ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)stu, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_925dfe_mi_0);


            block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, stu, 570425344));

        }

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_925dfe_mi_2);
    }
    return 0;
}

原因:
__main_block_impl_0里有Student类型的变量指针stu,所以block强引用了student对象,在断点处不会释放

从block角度深层次理解:

mrc环境下

   LYJBlock block ;
        {
            Student *stu = [Student new];
            stu.name = @"10";
            block = ^{
                NSLog(@"-----block内部==%@",stu.name);
            };
            [stu release];
        }
        
        NSLog(@"---------");//断点处

执行结果

2020-11-05 22:46:51.163798+0800 Blocktest[90785:792303] student被释放
(lldb) 

此时的block为stack类型的block也就是栈上的block,不会对对象强引用;但是对block执行copy操作,就不会执行释放。这是疑问执行copy操作后,变成堆上的block,内部会执行 [person retain]操作,使student引用计数增加1

不论mrc还是arc栈空间的block都不会强引用外部的变量

修改代码为

 LYJBlock block ;
        {
            Student *stu = [Student new];
            stu.name = @"10";
            
            __weak Student *weakStu = stu;
            block = ^{
                NSLog(@"-----block内部==%@",weakStu.name);
            };
        
        }
        
        NSLog(@"---------");

[图片上传失败...(image-6940b5-1605196903148)]
clang

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

int temp_ = 10;
static int temp1_ = 10;

typedef void(*LYJBlock)(void);

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Student *__weak weakStu;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Student *__weak _weakStu, int flags=0) : weakStu(_weakStu) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  Student *__weak weakStu = __cself->weakStu; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_40ef2f_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)weakStu, sel_registerName("name")));
            }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakStu, (void*)src->weakStu, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        LYJBlock block ;
        {
            Student *stu = ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"));
            ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)stu, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_40ef2f_mi_0);

            __attribute__((objc_ownership(weak))) Student *weakStu = stu;
            block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, weakStu, 570425344));

        }

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_40ef2f_mi_2);
    }
    return 0;
}

提前释放了,

因为block没有强引用stu,弱引用stu,所以arc大括号结束,stu释放了。为什么若引用就提前释放,因为弱引用_Block_object_assign函数会根据你是弱引用,不会对stu机型引用计数加1,
而强引用会_Block_object_assign会对stu对象进行引用计数加1,所以不会释放

总结
当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
image.png

5.1.1 易错点

iOS工程

#import "LYJPerson.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    

}


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    LYJPerson *p = [[LYJPerson alloc] init];
    
    __weak LYJPerson *weakP = p;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"1-------%@", p);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"2-------%@", weakP);
        });
    });
    
    NSLog(@"touchesBegan:withEvent:");
}

执行结果

2020-11-06 00:02:17.931720+0800 Interview03-测试[95455:877623] touchesBegan:withEvent:
2020-11-06 00:02:18.931785+0800 Interview03-测试[95455:877623] 1-------
2020-11-06 00:02:18.932006+0800 Interview03-测试[95455:877623] LYJPerson - dealloc
2020-11-06 00:02:20.932116+0800 Interview03-测试[95455:877623] 2-------(null)

可以看到1s后执行,对象销毁,2s打印对象为空

系统函数会对block执行copy到堆上,block对变量强引用,所以不会马上释放,1s后,block释放,所以强引用的block释放,对象释放,2s后,再次访问对象,已经释放

修改代码


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    LYJPerson *p = [[LYJPerson alloc] init];
    
    __weak LYJPerson *weakP = p;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"1-------%@", weakP);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"2-------%@", p);
        });
    });
    
    NSLog(@"touchesBegan:withEvent:");
}

执行结果

2020-11-06 00:12:47.961397+0800 Interview03-测试[96023:890589] touchesBegan:withEvent:
2020-11-06 00:12:49.059968+0800 Interview03-测试[96023:890589] 1-------
2020-11-06 00:12:51.060395+0800 Interview03-测试[96023:890589] 2-------
2020-11-06 00:12:51.060665+0800 Interview03-测试[96023:890589] LYJPerson - dealloc

系统函数会对block执行copy到堆上,block对变量强引用,所以不会马上释放,1s后,访问弱引用,不会引用计数产生影响,2s强应用才产生影响,所以3s后释放

5.2 __block修饰符

int类型变量,在block内部修改时,会提示错误,如下


image.png

由2.1可知block内部执行逻辑在__main_block_func_0中,而局部变量是在main,所以无法修改

修改方法

  1. 变成static,原因从2.2可知,指针传递捕获变量
  2. 变成全局变量,原因2.3可知原因
  3. __block修饰

方法1和2弊端是,会一直在内存中,所以一般采用第三种方案

方案3我们通过clang查看c++代码为简称:BlockA

struct __Block_byref_temp_0 {
  void *__isa;
__Block_byref_temp_0 *__forwarding;
 int __flags;
 int __size;
 int temp;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_temp_0 *temp; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_temp_0 *_temp, int flags=0) : temp(_temp->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  __Block_byref_temp_0 *temp = __cself->temp; // bound by ref

             (temp->__forwarding->temp) = 30;
                   NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_2ba61f_mi_0,(temp->__forwarding->temp));
               }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->temp, (void*)src->temp, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->temp, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_temp_0 temp = {(void*)0,(__Block_byref_temp_0 *)&temp, 0, sizeof(__Block_byref_temp_0), 10};
         void(*block)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_temp_0 *)&temp, 570425344));
          (temp.__forwarding->temp) = 20;
       ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
    }
    return 0;
}

查看c++代码可知,

  1. temp被包装成了__Block_byref_temp_0 *temp对象,成为了一个指针对象,对象里有一个int变量temp

  2. 定义temp的地方变成__attribute__((__blocks__(byref))) __Block_byref_temp_0 temp = {(void*)0,(__Block_byref_temp_0 *)&temp, 0, sizeof(__Block_byref_temp_0), 10};简化后为__Block_byref_temp_0 temp = { 0, &temp, 0, sizeof(__Block_byref_temp_0), 10}

  3. 定义block部分可简化为void(*block)(int,int) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, &temp, 570425344));将第2步定义的结构体地址传入给__main_block_impl_0temp指针

  4. 修改地方(temp->__forwarding->temp) = 30,通过__forwarding指针修改temp变量

5.2.1扩展1

当修改代码为

  
        __block int temp = 10;
        __block NSObject * obj = [NSObject new];
         void(^block)(int,int) = ^(int a ,int b){
             temp = 30;
             obj = nil;
                   NSLog(@"这是一个block,---%d",temp);
               };
          temp = 20;
       block(10,10);
    }

对应

struct __Block_byref_temp_0 {
  void *__isa;
__Block_byref_temp_0 *__forwarding;
 int __flags;
 int __size;
 int temp;
};
struct __Block_byref_obj_1 {
  void *__isa;
__Block_byref_obj_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_temp_0 *temp; // by ref
  __Block_byref_obj_1 *obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_temp_0 *_temp, __Block_byref_obj_1 *_obj, int flags=0) : temp(_temp->__forwarding), obj(_obj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  __Block_byref_temp_0 *temp = __cself->temp; // bound by ref
  __Block_byref_obj_1 *obj = __cself->obj; // bound by ref

             (temp->__forwarding->temp) = 30;
             (obj->__forwarding->obj) = __null;
                   NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_b33668_mi_0,(temp->__forwarding->temp));
               }

5.2.2 扩展2

如果此时最后打印temp的地址是谁的地址呢

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       
        __block int temp = 10;
        __block NSObject * obj = [NSObject new];
         void(^block)(int,int) = ^(int a ,int b){
             temp = 30;
             obj = nil;
                   NSLog(@"这是一个block,---%d",temp);
               };
          temp = 20;
       block(10,10);
        NSLog(@"%p", &temp);
    }
    return 0;
}

__Block_byref_temp_0中的int变量temp的地址,猜测苹果屏蔽内部实现,给开发者感知他的类型没变,给kvo类似

image.png

总结

  • __block可以用于解决block内部无法修改auto变量值的问题
    *__block不能修饰全局变量、静态变量(static)
  • 编译器会将__block变量包装成一个对象

5.3内存管理

  • 当block在栈上时,并不会对__block变量产生强引用

  • 当block被copy到堆时

    • 会调用block内部的copy函数
    • copy函数内部会调用_Block_object_assign函数
    • _Block_object_assign函数会对__block变量形成强引用(retain)

    copy的流程如下

    • block0和block1分别指向__block变量
    • block0进行指向__block的时候,arc进行copy,会将block0拷贝到堆上,并且将指向的__block也拷贝到堆上
    • block1进行指向__block的时候,arc进行copy,会将block1拷贝到堆上,由于__block已经拷贝到堆上,所以不再拷贝
    • forwading指针原来指向自己栈上
    • 复制到堆上后,栈上的block的fording指针指向堆上block,当然堆上的block还是指向堆上
      也解释了为什么要设计成forwarding指针这种模式
image.png
image.png
image.png
image.png

当block从堆中移除时

  • 会调用block内部的dispose函数
  • dispose函数内部会调用_Block_object_dispose函数
  • _Block_object_dispose函数会自动释放引用的__block变量(release)

分为以下两个情况

image.png

情况1,只有一个引用,__block没有持有者而被废弃

image.png

情况2,block0被废弃的时候,会对__block变量进行release操作,是的__block引用计数减1,但是还有被应用,所以没有被销毁,当block1页销毁的时候,也对__block变量进行release操作,他的引用计数也减1,这时候减少为0,也就是没持有者,所以__block此时进行销毁

5.3.1 __block和对象类型比较

编写代码

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       __block int temp = 10;
        NSObject *obj = [NSObject new];
        __weak NSObject *weakObj = obj;
        void(^block)(void) = ^(void){
            NSLog(@"%d",temp);
            NSLog(@"%@",weakObj);
              };
      block();
    }
    return 0;
}

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m 操作后


struct __Block_byref_temp_0 {
  void *__isa;
__Block_byref_temp_0 *__forwarding;
 int __flags;
 int __size;
 int temp;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *__weak weakObj;
  __Block_byref_temp_0 *temp; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__weak _weakObj, __Block_byref_temp_0 *_temp, int flags=0) : weakObj(_weakObj), temp(_temp->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_temp_0 *temp = __cself->temp; // bound by ref
  NSObject *__weak weakObj = __cself->weakObj; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_03f8f3_mi_0,(temp->__forwarding->temp));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_03f8f3_mi_1,weakObj);
              }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->temp, (void*)src->temp, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->weakObj, (void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->temp, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
       __attribute__((__blocks__(byref))) __Block_byref_temp_0 temp = {(void*)0,(__Block_byref_temp_0 *)&temp, 0, sizeof(__Block_byref_temp_0), 10};
        NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
        __attribute__((objc_ownership(weak))) NSObject *weakObj = obj;
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, weakObj, (__Block_byref_temp_0 *)&temp, 570425344));
      ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

通过
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->temp, (void*)src->temp, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->weakObj, (void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

  • 看到__block类型是block直接对temp对象强引用
  • 对象类型,是根据对象是weak类型还是strong类型,进行强引用还是弱引用

5.3.2 对象类型的auto变量、__block变量总结

  • 当block在栈上时,对它们都不会产生强引用
  • 当block拷贝到堆上时,都会通过copy函数来处理它们
    • __block变量(假设变量名叫做a)
    • _Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/);
  • 对象类型的auto变量(假设变量名叫做p)
    • _Block_object_assign((void)&dst->p, (void)src->p, 3/BLOCK_FIELD_IS_OBJECT/);
  • 当block从堆上移除时,都会通过dispose函数来释放它们
    • __block变量(假设变量名叫做a)
    • _Block_object_dispose((void)src->a, 8/BLOCK_FIELD_IS_BYREF*/);
  • 对象类型的auto变量(假设变量名叫做p)
    • _Block_object_dispose((void)src->p, 3/BLOCK_FIELD_IS_OBJECT*/);
image.png

5.3.3 被__block修饰的对象类型

  • 当__block变量在栈上时,不会对指向的对象产生强引用
  • 当__block变量被copy到堆时
    • 会调用__block变量内部的copy函数
    • copy函数内部会调用_Block_object_assign函数
    • _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)
  • 如果__block变量从堆上移除
    • 会调用__block变量内部的dispose函数
    • dispose函数内部会调用_Block_object_dispose函数
    • _Block_object_dispose函数会自动释放指向的对象(release)

准备Student代码,方便观看是否销毁


@implementation Student

- (void)dealloc
{
    NSLog(@"我销毁了==%s",__func__);
}
@end

编写测试代码

#import "Student.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
       __block Student *stu = [Student new];
        void(^block)(void) = ^(void){
            NSLog(@"stu===%p",stu);
        };
        block();
    } 
    return 0;
}

clang之后的代码为

struct __Block_byref_stu_0 {
  void *__isa;
__Block_byref_stu_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Student *__strong stu;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_stu_0 *stu; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_stu_0 *_stu, int flags=0) : stu(_stu->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_stu_0 *stu = __cself->stu; // bound by ref

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_098af5_mi_0,(stu->__forwarding->stu));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->stu, (void*)src->stu, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->stu, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

       __attribute__((__blocks__(byref))) __Block_byref_stu_0 stu = {(void*)0,(__Block_byref_stu_0 *)&stu, 33554432, sizeof(__Block_byref_stu_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"))};
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_stu_0 *)&stu, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

此时的内存的stu对象指向关系为

image.png
  • 通过__main_block_impl_0__Block_byref_stu_0知道
  • block内部有一个__Block_byref_stu_0类型的stu指针
  • stu指针指向的结构体里有一个跟外部引用类型一致的stu对象
  • 该stu对象指向我们在栈上new查出来的对象

此时修改代码为弱引用

#import "Student.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Student *stu = [Student new];
        __block __weak Student *weakStu = stu;
        void(^block)(void) = ^(void){
            NSLog(@"stu===%p",weakStu);
        };
        block();
    } 
    return 0;
}

c++代码变为

struct __Block_byref_weakStu_0 {
  void *__isa;
__Block_byref_weakStu_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Student *__weak weakStu;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_weakStu_0 *weakStu; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakStu_0 *_weakStu, int flags=0) : weakStu(_weakStu->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

此时的结构就变成

image.png
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        Student *stu = ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"));
        __attribute__((__blocks__(byref))) __attribute__((objc_ownership(weak))) __Block_byref_weakStu_0 weakStu = {(void*)0,(__Block_byref_weakStu_0 *)&weakStu, 33554432, sizeof(__Block_byref_weakStu_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, stu};
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_weakStu_0 *)&weakStu, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    
    static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

struct __Block_byref_weakStu_0 {
  void *__isa;//8个字节
__Block_byref_weakStu_0 *__forwarding;//8个字节
 int __flags;//4个字节
 int __size;//4个字节
 void (*__Block_byref_id_object_copy)(void*, void*);//8个字节
 void (*__Block_byref_id_object_dispose)(void*);//8个字节
 Student *__weak weakStu;
};
    

具体流程是

  • block拷贝到堆上时,调用block内部的Desc的__main_block_copy_0进行copy操作,进行强引用结构体__Block_byref_weakStu_0
  • __Block_byref_weakStu_0被拷贝到堆上时,会调用它内部的__Block_byref_id_object_copy进行copy,对指向的对象进行强或者弱引用
  • __Block_byref_id_object_copy_131偏移40个字节,也就是Student *__weak weakStu指向了我们的变量weakStu

销毁时

  • 会调用__block变量内部的dispose函数,对weakStu进行释放
  • weakStu释放时候,其指向的__Block_byref_weakStu_0也会进行自己内部的__Block_byref_id_object_dispose,对其指向的对象进行release操作

5.4循环引用

测试代码Student

#import 

NS_ASSUME_NONNULL_BEGIN
typedef void(^LYJBlock)(void);
@interface Student : NSObject
@property (nonatomic, copy) LYJBlock block;
@property (nonatomic, assign) int  age;
@end

NS_ASSUME_NONNULL_END
#import "Student.h"

@implementation Student

- (void)dealloc
{
    NSLog(@"我销毁了==%s",__func__);
}
@end

在main函数中

#import "Student.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Student *stu = [Student new];
        stu.age = 10;
       stu.block = ^(void){
            NSLog(@"age===%d",stu.age);
        };
        
    }
    NSLog(@"-----1111111----");
    return 0;
}

执行结果

2020-11-12 21:02:59.422862+0800 BLockTest[7117:97280] ---------
Program ended with exit code: 0

按道理执行到11111时候,大括号结束,stu应该释放,但是打印可看到没有释放,造成了循环引用。如图所示


image.png
  1. stu指向Student对象,Student对象里有一个成员变量_block,改block指向了block变量,block里又有一个strong类型的指针指向Student对象,如图形成了循环引用
  2. 大括号结束后,左下角的stu指向右边的Student的指针消失,但是右边还存在着相互引用,所以不会销毁

5.4.1 ARC环境循环引用解决

  1. __weak__unsafe_unretained解决
  2. __block解决(必须要调用block)

最好方案是__weak

原因:

  • __weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
  • __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变

——weak方式
修改代码

#import "Student.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Student *stu = [Student new];
        stu.age = 10;
        __weak Student *weakStu = stu;
      //  __weak typeof(stu) weakStu = stu;//两种写法等效
       stu.block = ^(void){
            NSLog(@"age===%d",weakStu.age);
        };
        
    }
    NSLog(@"----1111111111----");
    return 0;
}

执行结果

2020-11-12 21:27:13.093870+0800 BLockTest[8878:125076] 我销毁了==-[Student dealloc]
2020-11-12 21:27:13.094279+0800 BLockTest[8878:125076] ----1111111111----
Program ended with exit code: 0

clang一下查看源码为


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Student *__weak weakStu;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Student *__weak _weakStu, int flags=0) : weakStu(_weakStu) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  Student *__weak weakStu = __cself->weakStu; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_e1adf2_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)weakStu, sel_registerName("age")));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakStu, (void*)src->weakStu, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        Student *stu = ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"));
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)stu, sel_registerName("setAge:"), 10);
        __attribute__((objc_ownership(weak))) Student *weakStu = stu;
       ((void (*)(id, SEL, LYJBlock _Nonnull))(void *)objc_msgSend)((id)stu, sel_registerName("setBlock:"), ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, weakStu, 570425344)));

    }
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_e1adf2_mi_1);
    return 0;
}

可以看到此时block内部的stu对象为弱引用,此时的结构图如图


image.png

执行完大括号后,左边引用的stu指针销毁,右边的_block指向block也销毁,相应的block也销毁

__unsafe_unretained方式
修改代码

#import "Student.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Student *stu = [Student new];
        stu.age = 10;
        __unsafe_unretained typeof(stu) weakStu = stu;
       stu.block = ^(void){
            NSLog(@"age===%d",weakStu.age);
        };
        
    }
    NSLog(@"----1111111111----");
    return 0;
}

执行结果

2020-11-12 21:34:08.318599+0800 BLockTest[9301:132968] 我销毁了==-[Student dealloc]
2020-11-12 21:34:08.319057+0800 BLockTest[9301:132968] ----1111111111----
Program ended with exit code: 0

查看c++代码


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Student *__unsafe_unretained weakStu;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Student *__unsafe_unretained _weakStu, int flags=0) : weakStu(_weakStu) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  Student *__unsafe_unretained weakStu = __cself->weakStu; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_abec9f_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)weakStu, sel_registerName("age")));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakStu, (void*)src->weakStu, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        Student *stu = ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"));
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)stu, sel_registerName("setAge:"), 10);
        __attribute__((objc_ownership(none))) typeof(stu) weakStu = stu;
       ((void (*)(id, SEL, LYJBlock _Nonnull))(void *)objc_msgSend)((id)stu, sel_registerName("setBlock:"), ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, weakStu, 570425344)));

    }
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_abec9f_mi_1);
    return 0;
}

可以发现block内部的变量为__unsafe_unretained类型,也不会产生强引用,此时的内存结构图也是

image.png

释放过程和weak类似
不一样的点在于

  • weak方式在指向的对象销毁时,会自动让指针置为nil,也就是block销毁时,weakStu指针会自动变成nil
  • __unsafe_unretained则指向的对象销毁时,指针存储的地址值不变,也就是block销毁时,weakStu指针地址不变,会不安全,再次访问weakStu会造成野指针异常
    __block方式
    修改代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block Student *stu = [Student new];
        stu.age = 10;
       stu.block = ^(void){
            NSLog(@"age===%d",stu.age);
           stu = nil;
        };
        
        stu.block();
    }
    NSLog(@"----1111111111----");
    return 0;
}

细节点在于block内部stu = nil;以及stu.block();这两个地方缺一不可
原因:
clang查看c++代码

struct __Block_byref_stu_0 {
  void *__isa;
__Block_byref_stu_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Student *__strong stu;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_stu_0 *stu; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_stu_0 *_stu, int flags=0) : stu(_stu->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_stu_0 *stu = __cself->stu; // bound by ref

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_508bae_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)(stu->__forwarding->stu), sel_registerName("age")));
           (stu->__forwarding->stu) = __null;
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->stu, (void*)src->stu, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->stu, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_stu_0 stu = {(void*)0,(__Block_byref_stu_0 *)&stu, 33554432, sizeof(__Block_byref_stu_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"))};
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)(stu.__forwarding->stu), sel_registerName("setAge:"), 10);
       ((void (*)(id, SEL, LYJBlock _Nonnull))(void *)objc_msgSend)((id)(stu.__forwarding->stu), sel_registerName("setBlock:"), ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_stu_0 *)&stu, 570425344)));

        ((LYJBlock (*)(id, SEL))(void *)objc_msgSend)((id)(stu.__forwarding->stu), sel_registerName("block"))();
    }
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_508bae_mi_1);
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

此时的内存结构为


image.png

当执行block()方法时候,如下gif所示,所以会释放

Nov-12-2020 22-00-22.gif

5.4.2 MRC环境循环引用解决

  1. __unsafe_unretained解决
  2. __block解决

mrc环境不支持__weak

__unsafe_unretained跟arc环境类似

__block解决

#import "Student.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block Student *stu = [Student new];
        stu.age = 10;
       stu.block = ^(void){
            NSLog(@"age===%d",stu.age);
          
        };
        
        [stu release];
    }
    NSLog(@"----1111111111----");
    return 0;
}

执行结果

2020-11-12 22:14:44.792007+0800 BLockTest[11955:180623] 我销毁了==-[Student dealloc]
2020-11-12 22:14:44.792391+0800 BLockTest[11955:180623] ----1111111111----
Program ended with exit code: 0

这是因为在5.3.3的

_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)

也就是__Block_byref_stu_0变量内部的`Student * stu不会对student对象进行retain操作
也就是此时的内存结构是

image.png

5.5 @weakify、@strongify实现原理

在平时开发中,我们也会遇到@weakify、@strongify,这两个关键字是RAC中避免Block循环引用而开发的2个宏,这2个宏的实现过程很牛,值得我们学习
源码

#define weakify(...) \
    rac_keywordify \
    metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)


#define strongify(...) \
    rac_keywordify \
    _Pragma("clang diagnostic push") \
    _Pragma("clang diagnostic ignored \"-Wshadow\"") \
    metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
    _Pragma("clang diagnostic pop")

5.5.1 weakify


#if DEBUG
#define rac_keywordify autoreleasepool {}
#else
#define rac_keywordify try {} @catch (...) {}
#endif

这里在debug模式下使用@autoreleasepool是为了维持编译器的分析能力,而使用@try/@catch 是为了防止插入一些不必要的autoreleasepool。rac_keywordify 实际上就是autoreleasepool {}
的宏替换。因为有了autoreleasepool {}的宏替换,所以weakify要加上@,形成@autoreleasepool {}。

#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
        metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)

__VA_ARGS__:总体来说就是将左边宏中 ... 的内容原样抄写在右边 __VA_ARGS__ 所在的位置。它是一个可变参数的宏,是新的C99规范中新增的,目前似乎只有gcc支持(VC从VC2005开始支持)。

那么我们使用@weakify(self)传入进去。__VA_ARGS__相当于self。此时我们可以把最新开始的weakify套下来。于是就变成了这样:

rac_weakify_,, __weak, __VA_ARGS__整体替换MACRO, SEP, CONTEXT, ...

这里需要注意的是,源码中就是给的两个","逗号是连着的,所以我们也要等效替换参数,相当于SEP是空值。

替换完成之后就是下面这个样子:

autoreleasepool {}
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self))(rac_weakify_, , __weak, self)

现在我们需要弄懂的就是metamacro_concatmetamacro_argcount是干什么用的。

继续看看metamacro_concat 的实现


#define metamacro_concat(A, B) \
        metamacro_concat_(A, B)


#define metamacro_concat_(A, B) A ## B


## 是宏连接符。举个例子:

假设宏定义为#define XNAME(n) x##n,代码为:XNAME(4),则在预编译时,宏发现XNAME(4)与XNAME(n)匹配,则令 n 为 4,然后将右边的n的内容也变为4,然后将整个XNAME(4)替换为 x##n,亦即 x4,故 最终结果为 XNAME(4) 变为 x4。所以A##B就是AB。

metamacro_argcount 的实现

#define metamacro_argcount(...) \
        metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)


#define metamacro_at(N, ...) \
        metamacro_concat(metamacro_at, N)(__VA_ARGS__)


metamacro_concat是上面讲过的连接符,那么metamacro_at, N = metamacro_atN,由于N = 20,于是metamacro_atN = metamacro_at20。

#define metamacro_at0(...) metamacro_head(__VA_ARGS__)
#define metamacro_at1(_0, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at2(_0, _1, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at3(_0, _1, _2, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at4(_0, _1, _2, _3, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at5(_0, _1, _2, _3, _4, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at6(_0, _1, _2, _3, _4, _5, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at7(_0, _1, _2, _3, _4, _5, _6, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at8(_0, _1, _2, _3, _4, _5, _6, _7, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at9(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__)


metamacro_at20的作用就是截取前20个参数,剩下的参数传入metamacro_head。

#define metamacro_head(...) \
        metamacro_head_(__VA_ARGS__, 0)


#define metamacro_head_(FIRST, ...) FIRST

metamacro_head的作用返回第一个参数。返回到上一级metamacro_at20,如果我们从最源头的@weakify(self),传递进来,那么metamacro_at20(self,20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1),截取前20个参数,最后一个留给metamacro_head_(1),那么就应该返回1。

metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self)) = metamacro_concat(metamacro_foreach_cxt, 1) 最终可以替换成metamacro_foreach_cxt1。

在源码中继续搜寻。

// metamacro_foreach_cxt expansions
#define metamacro_foreach_cxt0(MACRO, SEP, CONTEXT)
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)

#define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
    metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \
    SEP \
    MACRO(1, CONTEXT, _1)

#define metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \
    metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
    SEP \
    MACRO(2, CONTEXT, _2)

#define metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
    metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \
    SEP \
    MACRO(3, CONTEXT, _3)

#define metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
    metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
    SEP \
    MACRO(4, CONTEXT, _4)

#define metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
    metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
    SEP \
    MACRO(5, CONTEXT, _5)

#define metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
    metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
    SEP \
    MACRO(6, CONTEXT, _6)

#define metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
    metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
    SEP \
    MACRO(7, CONTEXT, _7)

#define metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
    metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
    SEP \
    MACRO(8, CONTEXT, _8)

#define metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
    metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
    SEP \
    MACRO(9, CONTEXT, _9)

#define metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
    metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
    SEP \
    MACRO(10, CONTEXT, _10)

#define metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
    metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
    SEP \
    MACRO(11, CONTEXT, _11)

#define metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
    metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
    SEP \
    MACRO(12, CONTEXT, _12)

#define metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
    metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
    SEP \
    MACRO(13, CONTEXT, _13)

#define metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
    metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
    SEP \
    MACRO(14, CONTEXT, _14)

#define metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \
    metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
    SEP \
    MACRO(15, CONTEXT, _15)

#define metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \
    metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \
    SEP \
    MACRO(16, CONTEXT, _16)

#define metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \
    metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \
    SEP \
    MACRO(17, CONTEXT, _17)

#define metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \
    metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \
    SEP \
    MACRO(18, CONTEXT, _18)

#define metamacro_foreach_cxt20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \
    metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \
    SEP \
    MACRO(19, CONTEXT, _19)

metamacro_foreach_cxt这个宏定义有点像递归,这里可以看到N 最大就是20,于是metamacro_foreach_cxt19就是最大,metamacro_foreach_cxt19会生成rac_weakify_(0,__weak,18),然后再把前18个数传入metamacro_foreach_cxt18,并生成rac_weakify(0,__weak,_17),依次类推,一直递推到metamacro_foreach_cxt0。

#define metamacro\_foreach\_cxt0(MACRO, SEP, CONTEXT)

metamacro_foreach_cxt0就是终止条件,不做任何操作了。

于是最初的@weakify就被替换成

autoreleasepool {}
metamacro_foreach_cxt1(rac_weakify_, , __weak, self)

#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)

代入参数

autoreleasepool {}
rac_weakify_(0,__weak,self)

最终需要解析的就是rac_weakify_

#define rac_weakify_(INDEX, CONTEXT, VAR) \
    CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);

把(0,__weak,self)的参数替换进来(INDEX, CONTEXT, VAR)。
INDEX = 0, CONTEXT = __weak,VAR = self,

于是

CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);


//等效替换为


__weak __typeof__(self) self_weak_ = self;
最终@weakify(self) = __weak __typeof__(self) self_weak_ = self;


这里的self_weak_ 就完全等价于我们之前写的weakSelf。

5.5.2 strongify

rac_keywordify还是和weakify一样,是autoreleasepool {},只为了前面能加上@

_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
_Pragma("clang diagnostic pop")

strongify比weakify多了这些_Pragma语句。

关键字_Pragma是C99里面引入的。_Pragma比#pragma(在设计上)更加合理,因而功能也有所增强。

上面的等效替换

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshadow"
#pragma clang diagnostic pop


这里的clang语句的作用:忽略当一个局部变量或类型声明遮盖另一个变量的警告。

最初的

#define strongify(...) \
    rac_keywordify \
    _Pragma("clang diagnostic push") \
    _Pragma("clang diagnostic ignored \"-Wshadow\"") \
    metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
    _Pragma("clang diagnostic pop")

strongify里面需要弄清楚的就是metamacro_foreachrac_strongify_

#define metamacro_foreach(MACRO, SEP, ...) \
        metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)

#define rac_strongify_(INDEX, VAR) \
    __strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);

我们先替换一次,SEP = 空 , MACRO = rac_strongify_ , VA_ARGS , 于是替换成这样。

metamacro_foreach_cxt(metamacro_foreach_iter,,rac_strongify_,self)

根据之前分析,metamacro_foreach_cxt再次等效替换,metamacro_foreach_cxt##1(metamacro_foreach_iter,,rac_strongify_,self)

根据

#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)

再次替换成metamacro_foreach_iter(0, rac_strongify_, self)

继续看看metamacro_foreach_iter的实现

#define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG)

最终替换成rac_strongify_(0,self)

#define rac_strongify_(INDEX, VAR) \
    __strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);

INDEX = 0, VAR = self,于是@strongify(self)就等价于

__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);

//等价于

__strong __typeof__(self) self = self_weak_;

注意@strongify(self)只能使用在block中,如果用在block外面,会报错,因为这里会提示你Redefinition of 'self'。

5.5.3 总结

@weakify(self) = @autoreleasepool{} __weak __typeof__ (self) self_weak_ = self;

@strongify(self) = @autoreleasepool{} __strong __typeof__(self) self = self_weak_;

经过分析以后,其实@weakify(self) 和 @strongify(self) 就是比我们日常写的weakSelf、strongSelf多了一个@autoreleasepool{}而已,至于为何要用这些复杂的宏定义来做,目前我还没有理解。如果有大神指导其中的原因,还请多多指点

你可能感兴趣的:(刨根问底block)