iOS原理 - 探索Block

155 阅读11分钟

探索OC对象的实现原理的最好方式就是查看它的C++代码实现,下面使用该方式探索一下Block的实现原理。

Block捕获自动变量

OC源码:

#import <Foundation/Foundation.h>

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

    int lucy = 128;
    int jack = 256; //没有在block中使用
    NSString *name = @"david";
    
    void (^blk)(void) = ^{
        NSLog(@"%d == %@",lucy,name);
    };
    blk();
    return 0;
}

进入到.m文件目录,clang -rewrite-objc main.m,会在当前目录中生成同名 .cpp 文件,打开 main.cpp 文件;

C++

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;  //今后版本升级所需的区域
  void *FuncPtr; //函数指针
};
...
...中间代码省略
...
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc; //对__main_block_impl_0结构体的一个描述信息
  int lucy;		//捕获了外部的自动变量
  NSString *name;	//捕获了外部的自动变量
  //block结构体构造函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _lucy, NSString *_name, int flags=0) : lucy(_lucy), name(_name) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int lucy = __cself->lucy; // bound by copy(值传递)
  NSString *name = __cself->name; // bound by copy(值传递)

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_f64995_mi_1,lucy,name);
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->name, (void*)src->name, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

static struct __main_block_desc_0 {
  size_t reserved;   //今后升级版本所需区域
  size_t Block_size; //block的大小
  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[]) {

    int lucy = 128;
    int jack = 256;
    NSString *name = (NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_f64995_mi_0;

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, lucy, name, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

解释:

  • 在block内部语法表达式中使用的自动变量(lucy,name)被作为成员变量追加到了__main_block_impl_0结构体中;

  • 变量 jack 没有在block中使用,所以没有出现在__main_block_impl_0结构体中;

  • 在初始化block结构体实例时(请看__main_block_impl_0的构造函数),还需要截获的自动变量lucy和name来初始化__main_block_impl_0结构体实例,因为增加了被截获的自动变量,block的体积会变大。

再来看一下block函数体的C++代码:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int lucy = __cself->lucy; // bound by copy(值传递)
  NSString *name = __cself->name; // bound by copy(值传递)

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_f64995_mi_1,lucy,name);
}

从这里看就更明显了:lucy,name 都是从__cself里面获取的,更说明了二者是属于block的。而且从注释来看(注释是由clang自动生成的),这两个变量是值传递,而不是指针传递,也就是说Block仅仅截获自动变量的值,所以这就解释了即使改变了外部的自动变量的值,也不会影响Block内部的值。

  • 在block外部改变自动变量的值
int main(int argc, const char * argv[]) {

    int lucy = 128;
    int jack = 256;
    NSString *name = @"david";

    void (^blk)(void) = ^{
        NSLog(@"%d == %@",lucy,name);
    };
    blk();
    lucy = 365;
    blk();	//输出不变
    return 0;
}
2020-11-13 12:31:46.649709+0800 BlockTest[46805:5753123] 128 == david
2020-11-13 12:31:46.650089+0800 BlockTest[46805:5753123] 128 == david
Program ended with exit code: 0

在block外部修改变量的值,不会影响block内部的输出;

  • 在block内部改变自动变量的值
int main(int argc, const char * argv[]) {

    int lucy = 128;
    int jack = 256;
    NSString *name = @"david";

    void (^blk)(void) = ^{
        NSLog(@"%d == %@",lucy,name);
        lucy = 333; //编译不通过
    };
    blk();
    return 0;
}

直接在block内改变lucy的值是无法编译的,错误提示 Variable is not assignable (missing __block type specifier)没有指定__block;

这是因为Block内部和外部的变量实际上是两种不同的存在:前者是Block内部结构体的一个成员变量,后者是在栈区里的临时变量。

由此得到结论:

  1. 在block外部修改变量的值,不会影响block内部
  2. 在block内无法改变外部自动变量的值

被截获的自动变量的值是无法直接修改的,但是有两个方法可以解决这个问题:

1.改变变量的存储区域

2.给变量添加__block修饰符

1. 改变变量的存储区域

oc源码:

int global_lucy = 128;  //全局变量
static int static_global_jack = 256;  //全局静态变量
static NSString *static_global_name = @"david";

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

    static int static_mark = 1024; //局部静态变量

    void (^blk)(void) = ^{
        global_lucy += 1;
        static_global_jack += 1;
        static_global_name = @"david father";
        static_mark += 1;
    };
    NSLog(@"%d = %d = %@ = %d",global_lucy,static_global_jack,static_global_name,static_mark);
    blk();
    NSLog(@"%d = %d = %@ = %d",global_lucy,static_global_jack,static_global_name,static_mark);
    return 0;
}

输出:

2020-11-13 12:47:27.417016+0800 BlockTest[47073:5760762] 128 = 256 = david = 1024
2020-11-13 12:47:27.417428+0800 BlockTest[47073:5760762] 129 = 257 = david father = 1025

看一下对应的c++源码:

int global_lucy = 128; //全局变量
static int static_global_jack = 256; //全局静态变量
static NSString *static_global_name = (NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_a4def2_mi_0;


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  int *static_mark; //捕获到 局部静态变量 ,这里是指针,不是值
  
  //block结构体构造函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_mark, int flags=0) : static_mark(_static_mark) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_mark = __cself->static_mark; // bound by copy
  
        global_lucy += 1;
        static_global_jack += 1;
        static_global_name = (NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_a4def2_mi_1;
        (*static_mark) += 1;
}

可以发现:

  1. 全局变量和全局静态变量没有被截获到block里面,它们的访问是不经过block的,请看__main_block_func_0函数体,没有通过 __cself 引用;
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_mark = __cself->static_mark; // bound by copy

        global_lucy += 1;
        static_global_jack += 1;
        static_global_name = (NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_a4def2_mi_1;
        (*static_mark) += 1;
}
  1. 访问局部静态变量(static_mark)时,是将局部静态变量的指针传递给__main_block_impl_0 结构体的构造函数,此时不是值传递,是指针传递;

int *static_mark; //局部静态变量(静态变量的指针)

(*static_mark) += 1; //指针所指向的变量 +1 操作

2. 给自动变量添加__block修饰符

oc源码:

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

    __block int mark = 1024;

    void (^blk)(void) = ^{
        mark += 1;
    };
    blk();
    NSLog(@"%d",mark);//输出 1025
    return 0;
}

可以看到是预期结果 1025,下面看下对应的c++源码: c++:

struct __Block_byref_mark_0 {
  void *__isa;
__Block_byref_mark_0 *__forwarding;
 int __flags;
 int __size;
 int mark;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_mark_0 *mark; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_mark_0 *_mark, int flags=0) : mark(_mark->__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_mark_0 *mark = __cself->mark; // bound by ref

        (mark->__forwarding->mark) += 1;
}
/*从栈复制到堆时调用*/
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
	_Block_object_assign((void*)&dst->mark, (void*)src->mark, 8/*BLOCK_FIELD_IS_BYREF*/);
}
/*堆上的Block被废弃时调用*/
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
	_Block_object_dispose((void*)src->mark, 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[]) {

    __attribute__((__blocks__(byref))) __Block_byref_mark_0 mark = {(void*)0,(__Block_byref_mark_0 *)&mark, 0, sizeof(__Block_byref_mark_0), 1024};

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_mark_0 *)&mark, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_a9066b_mi_0,(mark.__forwarding->mark));
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

注意,被__block修饰的变量会被转换为如下结构体:

struct __Block_byref_mark_0 {
  void *__isa;
__Block_byref_mark_0 *__forwarding;
 int __flags;
 int __size;
 int mark;
};

解释一下 __Block_byref_mark_0 的结构:

  1. __isa指针,
  2. __forwarding指针,通过__forwarding,可以实现无论__block变量配置在栈上还是堆上都能正确地访问__block变量,也就是说__forwarding是指向自身的。
  3. __flags,是标志性参数,暂时没用到所以默认为0。
  4. __size,是该结构体所占用的大小。
  5. 保存了最初的mark变量,也就是说原来单纯的int类型的mark变量被__block修饰后生成了一个结构体。这个结构体其中一个成员变量持有原来的mark变量。

结构体类型命名是__Block_byref_ + 变量名 + _ + index(变量出现的顺序)

注意:在__main_block_impl_o结构中并不是存储的__Block_byref_mark_0变量,而是存储的变量的指针地址,这样做是为了能够在多个block中访问到同一个__block变量;

需要特别注意的是__main_block_func_0函数的实现:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_mark_0 *mark = __cself->mark; // bound by ref

        (mark->__forwarding->mark) += 1;
}

这里首先依然是通过函数传入的__cself,取到内部的mark实例变量,然后使用mark->__forwarding->mark的形式赋值,为什么要这么做?

借用别人做的一个图:

  1. 最初__block变量在栈上时,它的成员变量__forwarding指向栈上的__block变量结构体实例。
  2. 当__block变量被复制到堆上时,会将__forwarding的值替换为堆上的__block变量结构体实例的地址,而在堆上的__block变量自己的__forwarding的值就指向它自己。

所以,mark->__forwarding->mark这样赋值,可以保证不管变量是否从栈区拷贝到了堆区,都能正确的访问实际的变量。

Block捕获对象

1. block中捕获NSArray对象

  • 往可变集合里面添加内容

oc代码:

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

    id array = [NSMutableArray new];
    void (^blkArr)(id) = ^(id object){
        [array addObject:object];
        NSLog(@"add > %ld",[object integerValue]);
    };
    blkArr(@"1");
    blkArr(@"2");
    NSLog(@"%@",array);
    return 0;
}
2020-11-13 19:19:45.942159+0800 BlockTest[14932:6098575] add > 1
2020-11-13 19:19:45.942595+0800 BlockTest[14932:6098575] add > 2
2020-11-13 19:19:45.942737+0800 BlockTest[14932:6098575] (
    1,
    2
)

发现在没有__block修饰的情况下,可以正常给数组对象添加值;

c++代码:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id array; //捕获了外部的array变量
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id object) {
  id array = __cself->array; // bound by copy

  ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)object);
   NSLog((NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_c2c16e_mi_0,((NSInteger (*)(id, SEL))(void *)objc_msgSend)((id)object, sel_registerName("integerValue")));
}

__main_block_impl_0结构体中可以看到截获的array;

再看 __main_block_func_0的函数实现,array 是从__cself里面获取的,说明array是属于block的;

  • 在block里面给外部集合对象赋值新的可变集合对象
int main(int argc, const char * argv[]) {

    id array = [NSMutableArray new];
    void (^blkArr)(id) = ^(id object){
        [array addObject:object];
        id array2 = [NSMutableArray arrayWithObjects:@"2", nil];
        array = array2; //编译错误
        NSLog(@"add > %ld",[object integerValue]);
    };

    blkArr(@"1");
    NSLog(@"%@",array);

    return 0;
}

同样,也会编译不通过;上面已经论述过,添加__block修饰就可以了;

2. 给对象添加__block修饰符

oc代码:

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

    __block id array = [NSMutableArray new];
    void (^blkArr)(id) = ^(id object){
        [array addObject:object];
        id array2 = [NSMutableArray new];
        [array2 addObject:@"2"];
        array = array2;
        NSLog(@"add >> %ld",[object integerValue]);
    };
    blkArr(@"1");
    NSLog(@"%@",array);

    return 0;
}
2020-11-13 19:30:26.536419+0800 BlockTest[15136:6104254] add >> 1
2020-11-13 19:30:26.536860+0800 BlockTest[15136:6104254] (
    2
)

成功显示了array2中的内容;

c++代码:

struct __Block_byref_array_0 {
  void *__isa;
__Block_byref_array_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id array;
};

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

  	__Block_byref_array_0 *array = __cself->array; // bound by ref

        ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)(array->__forwarding->array), sel_registerName("addObject:"), (id)object);
        id array2 = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("new"));
        ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array2, sel_registerName("addObject:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_ad1ffc_mi_0);
        (array->__forwarding->array) = array2;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_ad1ffc_mi_1,((NSInteger (*)(id, SEL))(void *)objc_msgSend)((id)object, sel_registerName("integerValue")));
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
	_Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
	_Block_object_dispose((void*)src->array, 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[]) {

    __attribute__((__blocks__(byref))) __Block_byref_array_0 array = {(void*)0,(__Block_byref_array_0 *)&array, 33554432, sizeof(__Block_byref_array_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("new"))};
    void (*blkArr)(id) = ((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_array_0 *)&array, 570425344));
    ((void (*)(__block_impl *, id))((__block_impl *)blkArr)->FuncPtr)((__block_impl *)blkArr, (NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_ad1ffc_mi_2);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2d_98gwwpdj0h76rqc_6cs3607c0000gp_T_main_ad1ffc_mi_3,(array.__forwarding->array));

    return 0;
}

c++代码实现的结构和自动变量添加__block修饰类似;同样会生成__block变量的结构体:__Block_byref_array_0;

可以看到array被添加到了__main_block_impl_0结构体中,它是__Block_byref_array_0类型的;

顺便看下__main_block_func_0函数实现,也使用了(array->__forwarding->array) = array2;的方式进行赋值操作;

总结

  • 被__block 修饰的 自动变量、非集合、集合对象都可以赋值。
  • 没有__block修饰的集合可以修改其中的对象,但不能赋值,赋值会编译报错。
  • 使用__forwarding方式赋值可以保证不管变量是否从栈区拷贝到了堆区,都能正确的访问实际的变量。
  • __Block_byref_xx_0结构体不在__main_block_impl_0结构体中,目的是为了使得在多个Block中使用该__block变量。

参考文献

《Objective-C 高级编程》干货三部曲(二):Blocks篇

block 内部修改值