Block内部实现原理探究

作者: 9fda4b908fad | 来源:发表于2017-02-23 22:54 被阅读277次

    导论

    block在我们日常开发过程中经常见到,UIView动画你可以见到,视图跳转你可以看到,多线程你也可以看到,但是似乎我们有的时候只会使用,对于其内部实现并没有那么清楚,接下来就让我们一起看看block内部到底做了些什么.由于本人才疏学浅,如有不对的地方,欢迎大家及时指正

    在本篇你能了解到

    开始进入今天的正式话题

    一. block内部实现的原理

    让我看下一下一段代码:

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

    so easy是不是,接下来我们把他转化成正在执行时的c/c++代码,将oc代码转化成c/c++代码的命令是:clang -rewrite-objc 文件名
    打开终端,cd到你要转化的文件的路径下,执行clang -rewrite-objc 文件名,此时你应该会得到一个cpp文件,这就是我们要的转换后的代码了,恩,打开它

    cpp line number
    我一点都不觉得他so easy,哈哈

    不用紧张,我们不需要一行一行去看它,关键代码下面我已经贴出来了:

    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;
      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_qv__355hmw905bf0w6tmj8t8drr0000gn_T_main_35dd2d_mi_0,a);
            }
    
    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 a = 10;
    
            void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    
    • 1.我们开始定义的block,被赋值给了一个叫main_block_impl_0的结构体,这个结构体创建接收这样几个参数: void fp, struct __main_block_desc_0 desc, int _a, 最后一个flag默认等于0,然后为结构体内部的block_impl成员变量赋上相应的值,这个结构体就保存好我们的fp指针方便后面调用了,在创建这个结构体参数中我们需要注意的是void fp和int _a这两个参数*.

    • 2 .可以看到我们传了一个叫main_block_func_0的函数给fp指针,这个函数保存了我们需要执行的代码,这个函数又接收一个叫main_block_impl_0 *的结构体,这个结构体叫cself,这个cself就是我们刚才创建的结构体了,如果不好理解的话就理解成oc中的self吧,到这里我们这个函数里面就可以拿到刚才我们创建的结构体了

    • 3 .再看另外一个参数int _a: a(_a)是c++语法,意思是把_a的值赋值给a, 创建结构体的时候是直接把外面a的值传进去了,然后结构体把a的值保存下来

    • 4 . 最后回到block调用上来,我们可以看到block被转化成(__block_impl *)类型的结构体,从里面的FuncPtr指针取出刚才保存的fp指针调用,然后把block传进去,到这我们已经成功调到了我们刚才保存的函数了,最后一步是从结构体中取出我们刚才保存的a值打印,这样,一个block从创建到调用的流程就完成了

    到这我们可以了解到的几点是:
    • 1 . block其实就是一个指向结构体的指针
    • 2 . 也解释了为什么我们为什么在不加任何修饰词的时候,在block内部不能修改外部变量的原因.既然是以常量的形式传进block,赋值给结构体内部的成员变量,但是我们如果在block内部赋值,是赋给传进去的常量,常量给常量赋值,显然是不行的

    二 . __block到底做了什么

    以同样的方法转化成c++代码:我的源代码是这样的

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            __block int a = 10;
            
            void (^block)() = ^{
                
                a = 20;
                
                NSLog(@"%d",a);
            };
            
            block();
        }
        return 0;
    }
    
    

    这是转化后的代码:

    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) = 20;
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_qv__355hmw905bf0w6tmj8t8drr0000gn_T_main_2f1fbb_mi_0,(a->__forwarding->a));
            }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    
            void (*block)() = ((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 *)block)->FuncPtr)((__block_impl *)block);
        }
        return 0;
    }
    

    同样我们一步一步来看:

    • 1 .这里我们多了一个Block_byref_a_0类型的结构体,在我们加上 __block关键词之后,会创建一个Block_byref_a_0类型的结构体,我们重点关注一下创建时的第二个参数(__Block_byref_a_0 *)&a,这个表面,在创建该结构体的时候,内部会保存这个结构体的地址

    • 2 . 再到我们创建block这步,第三个参数传进去的是(__Block_byref_a_0 *)&a,也就是说,刚才我们创建的结构体的地址会传进来,接着它把_a -> __forwarding给了当前结构体的a变量,也就是__Block_byref_a_0 *a; // by ref这一句,接着看_a -> __forwarding这个成员变量对应的是什么,没错,就是我们刚才创建Block_byref_a_0这个类型的结构体时保存该结构体地址的变量

    • 3 . 然后我们调用_main_block_func_0(调用block跟上面block原理描述一样,这里不多赘述),这个函数,即调用block,我们找到_cself->a,然后再(a->_forwarding->a) = 20,回想一下刚才就知道_cself->a保存的就是上面创建的Block_byref_a_0这个结构体的地址,然后a->_forwarding也是a结构体的地址,不过一个是保存在_main_block_impl_0这个结构体中,一个保存在_Block_byref_a_0这个结构体中而已,接着,找到_Block_byref_a_0这个结构体中的a进行赋值,最后打印的也是这个值,流程和结果完全没有问题.所以__block可以修改block内部的值.

    • 以上部分就是关于__block在block中的具体分析

    最后,希望大家都能理解block更具体的一点东西,如果有不理解,请在下方留言,我会尽力解答的,或者文章有不对的,请多指正,我会及时修改,如果大家有兴趣,后续有时间的话,我再更新一下block循环引用部分,周末愉快.

    相关文章

      网友评论

      本文标题:Block内部实现原理探究

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