美文网首页iOS学习iOS学习专题iOS Developer
Block学习笔记3-自动变量的截取

Block学习笔记3-自动变量的截取

作者: Shirley_y | 来源:发表于2017-02-18 11:11 被阅读21次

    前文中所列举的是最简单的block,方便先理解block的结构,下面讲解一下在block中使用外部变量与block的作用域。

    1.截获自动变量

    OC源码:
    int i = 1;
    void (^bBlock)() = ^{
                           NSLog(@"%d",i);
                         };
    i=2;
    bBlock();//输出1,说明了在block声明时已经将自动变量值复制到了block变量中,所以以后再修改被截获的值时也无法影响到block的执行部分了
    转换后的C源码:
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int i;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int i = __cself->i; // bound by copy截获自动变量
      NSLog((NSString *)&__NSConstantStringImpl__var_folders_nf_mb711n6121z6123jpp8tbqrr0000gn_T_main_107bae_mi_0,i);
              }
    
    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, char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
               int i = 1;
               void (*bBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
               i=2;
               ((void (*)(__block_impl *))((__block_impl *)bBlock)->FuncPtr)((__block_impl *)bBlock);//6.block调用
            //(*bBlock->impl.FuncPtr)(bBlock)
        }
        return 0;
    }
    

    根据与前文中的代码比较,可以发现,block的执行函数中调用了外部变量i,转换后的C源码中,在构造__main_block_impl_0结构体时,就已经将i的值传递进去了,所以并不是在执行block时才去获取的i值,而是声明的时候就已经将引用的外部变量值copy进block了。所以也可以解释为什么在block声明后修改了i的值但是输出的block结果还是之前获取到的i值了。这就是截获自动变量的概念。
    如果试图在block中修改截取到的自动变量的值,如:

    int i = 1;
    void (^bBlock)() = ^{
                           i = i + 1;
                           NSLog(@"%d",i);
                         };
    

    编译器会给出

    修改自动变量值报错.png

    这个错误,提示缺少了_ _block修饰符,有关__block修饰符的稍后在讲,现在需要明确的就是block可以截获自动变量的值,且无法修改。
    但对于这三种变量是可以修改的,分别是全局变量,静态全局变量和静态变量
    如:

    int globalData = 1;
    static int staticGlobalData = 1;
    int main(int argc, char * argv[]) {
        @autoreleasepool {        
            static int j = 1;
                void (^bBlock)() = ^{
                    j = j+1;
                    globalData  =globalData + 1;
                    staticGlobalData = staticGlobalData + 1;
                    NSLog(@"j=%d",j);
                    NSLog(@"globalData=%d",globalData);
                    NSLog(@"staticGlobalData=%d",staticGlobalData);
                };
            bBlock();
    }
    //输出:
    //j=2
    //globalData=2
    //staticGlobalData=2
    

    对于全局变量与静态全局变量而言由于作用域是全局,所以可以在block中修改其值,但对于静态变量而言,转换后的代码如下:

    int globalData = 1;
    static int staticGlobalData = 1;
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int *j;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_j, int flags=0) : j(_j) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int *j = __cself->j; // bound by copy注意!
    
                    (*j) = (*j)+1;
                    globalData =globalData + 1;
                    staticGlobalData = staticGlobalData + 1;
                    NSLog((NSString *)&__NSConstantStringImpl__var_folders_nf_mb711n6121z6123jpp8tbqrr0000gn_T_main_0b992e_mi_0,(*j));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_nf_mb711n6121z6123jpp8tbqrr0000gn_T_main_0b992e_mi_1,globalData);
                    NSLog((NSString *)&__NSConstantStringImpl__var_folders_nf_mb711n6121z6123jpp8tbqrr0000gn_T_main_0b992e_mi_2,staticGlobalData);
                }
    
    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, char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            static int j = 1;
                void (*bBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &j));
    
            ((void (*)(__block_impl *))((__block_impl *)bBlock)->FuncPtr)((__block_impl *)bBlock);
        }
        return 0;
    }
    

    在代码中标注“注意”的地方即可看到区别,当block中访问的自动变量为静态变量时,加入到__main_block_func_0结构体中的为其指针,所以可以通过(*j) = (*j)+1;的方式来修改静态变量的值,这就是超出变量作用域时使用变量的最简单的方法。

    2.__block说明符

    对于__block说明符,我们都知道加上了它就可以在block中修改引用的外部变量的值,但是具体是怎么实现的呢?下面附上源码说明

    OC源码:
    __block int i = 1;
            void (^bBlock)() = ^{
                NSLog(@"%d",i);
                i=3;
                NSLog(@"%d",i);
            };
            i=2;
            bBlock();//输出2,3,__block关键字复制的是自动变量的指针
    C源码:
    struct __Block_byref_i_0 {//标注3
      void *__isa;
    __Block_byref_i_0 *__forwarding;
      int __flags;
      int __size;
      int i;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_i_0 *i; //标注1
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {//标注2
        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_i_0 *i = __cself->i; // bound by ref
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_nf_mb711n6121z6123jpp8tbqrr0000gn_T_main_37e40a_mi_0,(i->__forwarding->i));
                (i->__forwarding->i)=3;//标注4
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_nf_mb711n6121z6123jpp8tbqrr0000gn_T_main_37e40a_mi_1,(i->__forwarding->i));
            }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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};//标注5
    
    int main(int argc, char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1};
            void (*bBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
            (i.__forwarding->i)=2;
            ((void (*)(__block_impl *))((__block_impl *)bBlock)->FuncPtr)((__block_impl *)bBlock);
        }
        return 0;
    }
    
    

    从输出结果来看,加上了_ _block说明符后,就好像变量的作用域包含了block内,再来看转换出来的C源码:
    标注1处可以看到,对比未加 __block说明符,在__main_block_impl_0结构体重多出了__Block_byref_i_0类型的i变量。
    标注2处可以看到在构造函数中,将i的__forwarding指针传递进了__main_block_impl_0结构体,而不是像之前未加__block时传递的仅为简单的值。
    标注3处来详细的看__Block_byref_i_0结构体,这就是__block变量变为的结构体实例。其中int i相当于变量i的值也就是被使用的值,__forwarding指针持有指向该实例自身的指针,通过成员变量__forwarding来访问成员变量。所以在之后的main函数中,对i值进行修改时(i.__forwarding->i)=2;,也是通过__forwarding指针来实现的,也就是说用__block说明符声明变量后,这个变量已经变成了一个block变量,无论是否在block内调用,对于这个变量的操作都得通过它自己约定的__forwarding来访问,也就可实现无论是否在block的函数中,都可正常修改i值。

    相关文章

      网友评论

        本文标题:Block学习笔记3-自动变量的截取

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