iOS Block实现原理

作者: 1江春水 | 来源:发表于2019-01-03 17:24 被阅读12次

    系列文章:
    iOS Block概念、语法及基本使用
    iOS Block __block说明符
    iOS Block存储域及循环引用

    本章会讲解以下几点:

    • Block实现原理
    • Block也是OC对象
    • 截获自动变量值

    Block实现原理

    要想知道Block的内部实现,需要知道Block编译完后是什么样子,使用clang可看到Block编译完后的代码实现。

    Block的语法看着特别,但它实际上是作为极普通的C语言源代码来处理的。通过支持Block的编译器,将含有Block语法的源代码转换为C语言的源代码,并作为极为普通的C语言源代码被编译。

    clang -rewrite-objc 文件名
    

    上述命令使用在命令行项目是没问题的,但是使用在single APP项目中会报错,UIKit/UIKit.h’ file not found

    在single APP项目中使用以下命令:

    模拟器 xcrun -sdk iphonesimulator clang -rewrite-objc xxx.m
    真机 xcrun -sdk iphoneos clang -rewrite-objc xxx.m
    

    使用命令后会生成一个 xxx.cpp的文件。

    来具体看下Block编译后的代码,先来看命令行项目:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            void (^blk)(void) = ^{
                printf("Block\n");
            };
            blk();
        }
        return 0;
    }
    

    上述代码是一个很简单的无返回值无参数的Block,下面是使用clang编译后的源代码,只把有用的部分贴出:

    struct __block_impl {
      void *isa;//block 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) {
    
                printf("Block\n");
            }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;//block大小
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));//block表达式转换
            ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);//block调用
        }
        return 0;
    }
    

    如上变换后的源代码,通过Block使用的匿名函数实际上被作为简单的C语言函数来处理,另外Block语法所属的函数名(此处为main,后边会贴上 函数名为 viewDidLoad)和该Block语法在该函数出现的顺序值来给经clang变换的函数命名。

    下面具体一步一步来看看上述代码:
    __main_block_impl_0结构体的声明:第一个成员变量是impl,第二个成员变量是Desc。

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
    };
    

    __block_impl 结构体的声明:

    struct __block_impl {
      void *isa;//block isa指针
      int Flags;
      int Reserved;
      void *FuncPtr;//函数指针
    };
    

    __main_block_desc_0 结构体声明:

    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;//block大小
    }
    

    __main_block_impl_0实例的构造函数:

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

    Block表达式生成部分:初始化一个__main_block_impl_0结构体实例并赋值给blk变量。

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    

    __main_block_func_0结构体声明,__main_block_func_0该函数的参数__cself是 __main_block_impl_0 结构体实例,该实例其实就是转换前的Block实例。

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
                printf("Block\n");
            }
    

    去掉转换部分:

    struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
    struct __main_block_impl_0 *blk = &temp;
    

    上边代码对应的源代码就是以下代码:

    void (^blk)(void) = ^{
                printf("Block\n");
            };
    

    再来看看Block调用部分:blk()

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

    去掉转换部分:

    (*blk->impl.FuncPtr)(blk);
    

    正如刚才看到的编译后的代码,block语法转换的__main_block_func_0函数的指针被赋值成员变量FuncPtr中,另外也说明了__main_block_func_0函数的参数__cself指向Block值。在调用该代码的源代码中可以看出Block正是作为参数进行了传递。

    Block也是OC对象

    impl.isa = &_NSConcreteStackBlock;
    

    只要你理解OC对象内的isa指针,你就能理解这里的isa指针。block的isa指针指向自己所属的Block类。
    OC对象都有一个isa指针,指向属于当前的类。以下是<objc/runtime.h>运行时 objc_class 结构体的声明。

    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class _Nullable super_class                              OBJC2_UNAVAILABLE;
        const char * _Nonnull name                               OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    }
    

    以下贴出single APP 使用命令编译后的源代码实现,使用命令:

    xcrun -sdk iphonesimulator clang -rewrite-objc ViewController.m
    
    源代码:
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        int (^block)(int,int) = ^ int (int a,int b) {
            return a + b;
        };
        int sum = block(1,2);
        NSLog(@"%d",sum);
    }
    
    编译后:
    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    static struct __ViewController__viewDidLoad_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};
    
    
    static int __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself, int a, int b) {
    
            return a + b;
        }
    
    struct __ViewController__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __ViewController__viewDidLoad_block_desc_0* Desc;
      __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
        ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
    
        int (*block)(int,int) = ((int (*)(int, int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA));
        int sum = ((int (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 1, 2);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_9k_z85dfkt91zd1j387gcxn8xkh0000gn_T_ViewController_b0d5a8_mi_0,sum);
    }
    
    

    截获自动变量值(瞬间值)

    编译以下源代码:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int dmy = 256;
            int val = 10;
            const char *fmt = "val = %d\n";
            void (^blk)(void) = ^{
                printf(fmt,val);
            };
            val = 2;
            fmt = "These value were changed. val = %d\n";
            blk();
        }
        return 0;
    }
    

    编译后:

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    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)};
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {//最终的函数指针调用
      const char *fmt = __cself->fmt; // bound by copy
      int val = __cself->val; // bound by copy
    
                printf(fmt,val);
            }
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      const char *fmt;
      int val;//传入的变量 val
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
            int dmy = 256;
            int val = 10;
            const char *fmt = "val = %d\n";
            void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));//结构体带着参数val初始化并赋值
            val = 2;
            fmt = "These value were changed. val = %d\n";
            ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);//函数指针调用
        }
        return 0;
    }
    

    这样来看就能想明白为什么val、fmt变量改变,而block表达式内部的变量值不改变了。即Block截获的自动变量值是变量的瞬间值,在编译完成后就已经确定了,即便改变Block内部用到的变量的值,Block调用的时候,表达式内部的变量值也不会改变。

    总结

    Block实质就是: 编译的时候编译器会把Block的表达式及Block变量,编译成一般的C语言结构体、函数,block调用就是函数指针调用。Block表达式中使用局部变量时,编译生成的结构体(__main_block_impl_0)会持有该变量,该结构体会带着默认值初始化结构体实例。

    请看下集!

    相关文章

      网友评论

        本文标题:iOS Block实现原理

        本文链接:https://www.haomeiwen.com/subject/bxmzlqtx.html