block

作者: 码农农农SL | 来源:发表于2019-06-13 17:42 被阅读0次

    block初体验

    定义和使用block很简单:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            void(^sl_block)(int) = ^(int a1){
                NSLog(@"aaaaa:%d",a1);
            };
            sl_block(12);
            NSLog(@"Hello, World!");
        }
        return 0;
    }
    

    但是作为一个有理想的programmer,不仅要知道其怎么用,还要知道其内在原理。

    揭开面纱

    首先,将定义了上述block的main.m函数用clang 转换为main.cpp:

    clang -rewrite-objc main.m
    

    在同目录下会生成一个同名的.cpp文件。
    打开cpp文件,找到main函数:

    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            void(*sl_block)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
            ((void (*)(__block_impl *, int))((__block_impl *)sl_block)->FuncPtr)((__block_impl *)sl_block, 12);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_3e93ec_mi_1);
        }
        return 0;
    }
    

    为了方便理解,先用typedef将其简化:

    typedef void(*SL_BLOCK_TYPE)(int);
    typedef void(*FuncPtr_TYPE)(__block_impl *, int);
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
            SL_BLOCK_TYPE sl_block = SL_BLOCK_TYPE &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
            
            (FuncPtr_TYPE((__block_impl *)sl_block)->FuncPtr)((__block_impl *)sl_block, 12);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_3e93ec_mi_1);
        }
        return 0;
    }
    

    先看第一句:

            SL_BLOCK_TYPE sl_block = SL_BLOCK_TYPE &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    
    

    找到其中的未知函数或者变量:__main_block_impl_0、__main_block_func_0、__main_block_desc_0_DATA,在文件中找到其定义。
    先看__main_block_impl_0:

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

    可以看出,他是一个结构体,而上述__main_block_impl_0((void *, void *)是调用了其构造函数,返回一个__main_block_impl_0类型的结构体,并对该结构体取地址作为变量sl_block的值。
    继续研究__main_block_impl_0结构体的内部定义,发现其包含另外两个结构体:

      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
    

    先看第一个,找到其具体定义:

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

    这个好理解,就是简单的结构体,里面包含了两个int型数据和两个指针。为了理解,不在类型之间跳来跳去,直接__block_impl结构体消除,即将__main_block_impl_0结构体定义为:

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

    然后看__main_block_desc_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)};
    

    这里不仅定义了一个结构体,还定义并初始化了一个全局变量__main_block_desc_0_DATA。
    因为这个结构体保存的信息为一个保留位reserved和block的size,就不做细研究。
    __main_block_impl_0结构体解析完了,回到刚刚的位置,三个位置变量,两个(__main_block_impl_0、__main_block_desc_0_DATA)已经知道是什么了,还剩下一个__main_block_func_0。在文件中找到这个关键字的定义:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a1) {
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_3e93ec_mi_0,a1);
            }
    

    很明显可以看出,这是一个函数,更确切的说,这就是block的实现部分。在sl_block的定义和赋值中可以看出,这个函数被当成参数传递给了及结构体__main_block_impl_0的构造函数,而结构体的构造函数中,又将该函数指针,赋值给了其成员变量FuncPtr。
    到这里,基本就很明朗了:
    定义一个block,实际上是定义了一个结构体,而block中要执行的代码,则被定义为一个函数,并且该函数的函数指针被结构体的FuncPtr成员所持有。
    最后,看看block是怎么被调用的吧:

    (FuncPtr_TYPE((__block_impl *)sl_block)->FuncPtr)((__block_impl *)sl_block, 12);
    

    这个应该很容易就看出来了,就是函数指针的调用。取sl_block结构体中的FuncPtr成员,然后调用。

    深入研究----捕捉外部变量

    我们都知道,在block中,我们可以使用一些外部变量,那么,这就有一个问题了,block是怎么捕捉外部变量的呢?
    将之前的main.m修改一下,再rewrite成cpp,细究。
    main.m

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int i_a = 111;
            int i_b = 222;
            void(^sl_block)(int) = ^(int a1){
                NSLog(@"%d==%d",i_a,i_b);
                NSLog(@"aaaaa:%d",a1);
            };
            sl_block(12);
            NSLog(@"Hello, World!");
        }
        return 0;
    }
    

    main.cpp

    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            int i_a = 111;
            int i_b = 222;
            void(*sl_block)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i_a, i_b));
            ((void (*)(__block_impl *, int))((__block_impl *)sl_block)->FuncPtr)((__block_impl *)sl_block, 12);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_10456f_mi_2);
        }
        return 0;
    }
    

    可以看出,__main_block_impl_0构造函数的参数发生了变化,传递了两个参数:i_a,、i_b。
    看看其结构体内部变化:

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

    结构体多了两个和外部变量同名同类型的成员变量。
    再看其实现部分:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a1) {
      int i_a = __cself->i_a; // bound by copy
      int i_b = __cself->i_b; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_10456f_mi_0,i_a,i_b);
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_10456f_mi_1,a1);
            }
    

    可以看出,block内部使用的实际上不是外部变量,而是其成员变量,即外部变量的副本(bound by copy)。
    综上所述,可以得出,block对于用到的外部变量,会将其保存为成员变量,以供block内部使用。

    __block关键字

    既然说到block对于外部变量的捕捉,就不得不提__block关键字。
    对于上述的例子,外部变量对于block是readonly的。那么怎么才能readwrite呢?apple在设计block的时候肯定是早就考虑到了这个的,__block就是为了解决外部变量的write问题。

    ps:在C语言的角度考虑这个问题

    其实block的实现,基本都是基于c和c++的,所以考虑这个问题,很自然就想到了c语言的值传递和地址传递。(有兴趣可自行研究)

    apple的实现

    修改main.m,并rewrite成cpp:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            __block int i_a = 111;
            int i_b = 222;
            void(^sl_block)(int) = ^(int a1){
                NSLog(@"%d==%d",i_a,i_b);
                NSLog(@"aaaaa:%d",a1);
                i_a= i_b + i_a;
            };
            sl_block(12);
            NSLog(@"Hello, World!");
        }
        return 0;
    }
    
    int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            __attribute__((__blocks__(byref))) __Block_byref_i_a_0 i_a = {(void*)0,(__Block_byref_i_a_0 *)&i_a, 0, sizeof(__Block_byref_i_a_0), 111};
            int i_b = 222;
            void(*sl_block)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i_b, (__Block_byref_i_a_0 *)&i_a, 570425344));
            ((void (*)(__block_impl *, int))((__block_impl *)sl_block)->FuncPtr)((__block_impl *)sl_block, 12);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_d79e13_mi_2);
        }
        return 0;
    }
    

    对比之前的,发现i_a变量,变成了一个结构体__Block_byref_i_a_0:

    struct __Block_byref_i_a_0 {
      void *__isa;
    __Block_byref_i_a_0 *__forwarding;
     int __flags;
     int __size;
     int i_a;
    };
    

    而且在传参的时候,也是变成了地址传递((__Block_byref_i_a_0 *)&i_a,)。这就为修改i_a的值奠定了基础。因为地址传参之后修改的肯定不再是副本,而是本身。
    再看看实现部分:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a1) {
      __Block_byref_i_a_0 *i_a = __cself->i_a; // bound by ref
      int i_b = __cself->i_b; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_d7c9ac_mi_0,(i_a->__forwarding->i_a),i_b);
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_l1_82ns1v1j2zs331rh8zrgd8l40000gn_T_main_d7c9ac_mi_1,a1);
                (i_a->__forwarding->i_a)= i_b + (i_a->__forwarding->i_a);
            }
    

    到这里基本就明了了,__block修饰的变量,传递方式变成了地址传递,也就可以达到readwrite的效果了~
    最后,(i_a->__forwarding->i_a)= i_b + (i_a->__forwarding->i_a);这个骚操作没搞懂,没有搞错的话,i_a和i_a->__forwarding是同一个地址。
    __forwarding指向自身的操作,是因为block有可能存放在栈上,也有可能存放在堆上,当block没有发生复制时,它指向栈上的对象,当block发生复制时,block指向堆上的对象。

    相关文章

      网友评论

          本文标题:block

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