• clang分析iOS的block实现


    iOS开发中,我们经常使用block,尤其在一些异步需要回调的场景,通过向方法传递一个block参数,在异步操作执行完成之后,回调这个block。

    block的基本使用

    我们常常在文件头部声明block的类型,比如

    typedef void(^blkTest)(void);
    
    • 1

    这里声明了一个没有返回值,没有参数的block,其后如果要使用block的话,可以声明一个block变量,并且调用它。

     blkTest blk = ^(){
         NSLog(@"block test");
     };
     blk();
    
    • 1
    • 2
    • 3
    • 4

    block可以作为方法的返回值,也可以作为方法的参数。在方法的内部,可以生成block类型的变量然后执行它。

    block的类型

    为了查看block的类型,我们可以调用superclass方法获取它的实际类型。

    NSLog(@"%@", [[[blk class] superclass] superclass]);
    
    • 1

    控制台打印的结果为NSObject,这也验证了block实际为NSObject类型,它是一个对象。

    block的实现

    为了探究block的底层实现,我们可以使用clang编译器把Objective-C代码转换为C++代码。先写好Objective-C代码,新建一个文件命名为blockTest.m,写入以下代码

    #import <Foundation/Foundation.h>
    
    typedef void(^blkTest)(void);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {        
            blkTest blk = ^(){
                NSLog(@"block test");
            };
            blk();
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    使用clang命令转换为C++代码

    clang -rewrite-objc blockTest.m
    
    • 1

    当前目录下生成了blockTest.cpp文件,打开这个文件,可以发现block的实现代码。

    typedef void(*blkTest)(void);
    
    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(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_bf_clxp1kcj2f1_vpz2ry8t08j00000gn_T_blockTest_6ba139_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)};
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            blkTest blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
            ((void (*)(__block_impl *))((__block_impl *)blk)->__main_block_func_0)((__block_impl *)blk);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    可以发现有三个结构体,分别为__block_impl,__main_block_impl_0,

    __main_block_desc_0。__main_block_impl_0持有了

    __block_impl和__main_block_desc_0,__main_block_impl_0实际上是block的实现方式。

    __block_impl里面保存了isa指针和FuncPtr函数指针,block的具体实现被封装成了函数__main_block_func_0,这个__main_block_func_0会传入__main_block_impl_0结构体用于初始化。

    在调用block的时候,实际执行的就是函数指针FuncPtr,也就是

    __main_block_func_0

    block的具体实现基本就清楚了,通过函数指针封装block的具体实现,并把函数指针传入到__main_block_impl_0结构体用于初始化。block的执行实际是找到这个函数指针并且调用它。

    在block的实际使用中,我们常常会捕获变量用于实际处理。修改Objective-C代码,加入变量捕获,看看block的C++实现有什么变化。

    #import <Foundation/Foundation.h>
    
    typedef void(^blkTest)(void);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool { 
            int a = 1;       
            blkTest blk = ^(){
                NSLog(@"%d", a);
                NSLog(@"block test");
            };
            blk();
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    代码中多了变量a,block中捕获并且打印了变量a的值。再次使用clang命令查看C++代码实现。

    clang -rewrite-objc blockTest.m
    
    • 1

    在生成的C++代码中,可以发现有了一些变化。主要是__main_block_impl_0中多了int a ,__main_block_func_0中多了对变量的取值和使用。

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int a;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
        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 = __cself->a; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_bf_clxp1kcj2f1_vpz2ry8t08j00000gn_T_blockTest_8cd99b_mi_0, a);
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_bf_clxp1kcj2f1_vpz2ry8t08j00000gn_T_blockTest_8cd99b_mi_1);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    可以发现,存在变量捕获的情况下,捕获的变量会传递到block结构体中。注意这里传递的值,而不是引用。捕获变量的情况下,如果修改了变量值,Xcode会报错。如果想修改捕获的变量的值,需要在变量前面加上__block声明,block前面是两个下划线。加了__block声明后,block的C++实现有什么变化呢?

    #import <Foundation/Foundation.h>
    
    typedef void(^blkTest)(void);
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool { 
            __block int a = 1;       
            blkTest blk = ^(){
                a = 2;
                NSLog(@"%d", a);
                NSLog(@"block test");
            };
            blk();
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    接着使用clang命令查看C++实现。

    clang -rewrite-objc blockTest.m
    
    • 1

    在生成的C++代码中,发生了一些变化。

    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;
     int __flags;
     int __size;
     int a;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_a_0 *a; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref
    
                (a->__forwarding->a) = 2;
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_bf_clxp1kcj2f1_vpz2ry8t08j00000gn_T_blockTest_f1cf0e_mi_0, (a->__forwarding->a));
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_bf_clxp1kcj2f1_vpz2ry8t08j00000gn_T_blockTest_f1cf0e_mi_1);
            }
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1};
            blkTest blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
            ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    可以发现多了__Block_byref_a_0结构体,__main_block_impl_0的初始化参数也多了这个结构体。变量a转换成结构体__Block_byref_a_0,并且传入__main_block_impl_0结构体。注意main函数的中代码,传递的是变量a的引用,而不是a的值,这样block就可以修改了变量a的值了。

     blkTest blk = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    
    • 1

    block的内存区域

    在iOS的内存分布中,有栈,堆,BSS,全局变量区,代码段。block的分类包括__NSMallocBlock__,NSGlobalBlock__和__NSStackBlock

    ARC环境下,对于没有捕获变量的block而言,默认是NSGlobalBlock类型,并且方法到全局变量区。对于捕获了变量的block而言,实际执行时会自动把NSStackBlock从栈上拷贝到堆上,变为NSMallocBlock类型,存储位置在堆上。

    block循环引用问题

    Objective-C的内存使用了计数的方式管理内存,如果block和变量相互持有的话,会产生循环引用,这个时候可以使用weak解除循环引用。

     __weak typeof (self) weakSelf = self;
    
    • 1

    在block中使用weakSelf不会导致循环引用的发生。

  • 相关阅读:
    Vue3的手脚架使用和组件父子间通信-插槽(Options API)学习笔记
    OpenHarmony自定义构建函数:@Builder装饰器
    java计算机毕业设计Internet快递柜管理系统MyBatis+系统+LW文档+源码+调试部署
    3款别出心裁的电脑软件,个个精选,让你眼前一亮
    Nginx这么香,还不知道怎么学?看完这份Nginx笔记你能立马上手
    Hadoop完全分布式运行模式
    Pinely Round 2 (Div. 1 + Div. 2) F. Divide, XOR, and Conquer(区间dp)
    《计算机体系结构量化研究方法第六版》1.3 计算机体系结构的定义
    Linux安装jrockit-jdk1.6.0_29-R28.2.0-4.1.0-linux-x64
    搭建LInux服务面板1Panel.
  • 原文地址:https://blog.csdn.net/u011608357/article/details/127718747