美文网首页
iOS-Block变量捕获

iOS-Block变量捕获

作者: litt1err | 来源:发表于2017-12-26 16:55 被阅读466次

    block对变量的捕获

    1:可以捕获不可以修改变量

    • 局部变量

    2:可以捕获且可以修改变量

    • 全局变量
    • 静态变量
    • __block修饰的局部变量

    原理分析:

    1. 局部变量为什么可以被捕获确不能修改

    int a = 10;
    void (^blcok)() = [^{
        NSLog(@"%d",a);
    } copy];
    
    a=20;
    
    blcok(); // log : a = 10
    

    结果应该大家都知道,但是为什么会这样呢?

    我们用clang转化之后看看

    局部变量访问.png

    从block定义来看

    void (*blcok)() = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__ZMX__blockTest_block_impl_0((void *)__ZMX__blockTest_block_func_0, &__ZMX__blockTest_block_desc_0_DATA, a)), sel_registerName("copy")); 
    

    block的实现是通过__ZMX__blockTest_block_impl_0结构体的构造方法来定义的,我们来看下这个结构体

    struct __ZMX__blockTest_block_impl_0 {
      struct __block_impl impl;
      struct __ZMX__blockTest_block_desc_0* Desc;
      int a;
      __ZMX__blockTest_block_impl_0(void *fp, struct __ZMX__blockTest_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    impt:

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    isa:指向Class的指针
    flags:一些标识
    reserced:保留的一些变量
    funcptr:函数指针
    

    __ZMX__blockTest_block_desc_0:

    static struct __ZMX__blockTest_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __ZMX__blockTest_block_desc_0_DATA = { 0, sizeof(struct __ZMX__blockTest_block_impl_0)};
    
    reserced:保留的一些变量
    size:内存大小
    

    __ZMX__blockTest_block_impl_0 构造方法

    我们可以看到这个构造方法有四个参数

    void *fp:函数指针
    struct __ZMX__blockTest_block_desc_0 *desc: desc结构体
    int _a: 变量
    int flags=0:标识 可以不传
    

    我们通过简化block的定义:

    void (*blcok)() = ((void (*)())&__ZMX__blockTest_block_impl_0((void *)__ZMX__blockTest_block_func_0, &__ZMX__blockTest_block_desc_0_DATA, a));
    

    可以看到,我们在定义的时候就已经将a作为参数传递进去了。也就是在定义的时候我们的block就获取到了a的值,而且不管后面怎么修改a的值。我们在block内部获取的a都是定义的时候传进来的值,这也就导致为什么block可以捕获局部变量却不可以修改的原因

    2.1 全局变量 可以被捕获也可以修改

    (void)blockTest
    {
    
        void (^blcok)() = [^{
            NSLog(@"%d",a);
        } copy];
        
        a = 20;
        
        blcok(); // log : 20
        
    } 
    

    我们用clang转化之后看看

    全局变量访问.png

    一样的部分我就不重复了,我们可以看到这个时候定义blcok的构造函数是没有传入之前的参数a

    我们调用block然后再去执行NSLog函数 = 上面__ZMX__blockTest_block_func_0函数,这时候a的值已经改为20了

    static void __ZMX__blockTest_block_func_0(struct __ZMX__blockTest_block_impl_0 *__cself) {
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_47_6nlw9jbn3fb7c8lb1km1rzmm0000gn_T_ZMX_70ee3a_mi_0,a);
        }
    
    

    很显然,在我们调用block的时候,如果你之前有修改a的值,那打印的一定是新值

    2.2 静态变量 可以被捕获也可以修改

     (void)blockTest
    {
        static int a = 10;
    
        void (^blcok)() = [^{
            NSLog(@"%d",a);
        } copy];
        
        a = 20;
        
        blcok(); //log : 20
        
    }
    

    我们用clang转化之后看看

    静态变量访问.png

    通过构造函数我们可以看到,这时候入参多了一个int *_a,传递的是a的地址了。打印的函数__ZMX__blockTest_block_func_0也一样,都是获取到同一内存地址上的值操作。so,我们既可以访问a同时也可以修改a了

    2.3 __block修饰的变量 可以被捕获也可以修改

    (void)blockTest
    {
        __block int a = 10;
    
        void (^blcok)() = [^{
            NSLog(@"%d",a);
        } copy];
        
        a = 20;
        
        blcok();// log : 20
        
    }
    

    我们用clang转化之后看看

    __block修饰的局部变量访问.png

    哎!这时候的结构体__ZMX__blockTest_block_impl_0a变成了一个结构体指针。好奇怪,我们来看一下这个结构体

    struct __Block_byref_a_0 {
      void *__isa;
    __Block_byref_a_0 *__forwarding;
     int __flags;
     int __size;
     int a;
    };
    
    isa: 指向Class指针
    forwarding: 是指向a地址的指针
    flags:标识
    size:大小
    a: 变量
    

    我们再来看一下 我们blockTest函数

    static void _I_ZMX_blockTest(ZMX * self, SEL _cmd) {
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    
        void (*blcok)() = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__ZMX__blockTest_block_impl_0((void *)__ZMX__blockTest_block_func_0, &__ZMX__blockTest_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)), sel_registerName("copy"));
    
        (a.__forwarding->a) = 20;
    
        ((void (*)(__block_impl *))((__block_impl *)blcok)->FuncPtr)((__block_impl *)blcok);
    
    }
    

    这时候变量a变成了一个__Block_byref_a_0结构体,可以看到我们初始化的时候给a的地址跟a的值都传进去了

    a = 20 -> (a.__forwarding->a) = 20
    再次赋值我们是通过修改a指向的内存地址上的value来修改a的值

    打印函数

    static void __ZMX__blockTest_block_func_0(struct __ZMX__blockTest_block_impl_0 *__cself) {
      __Block_byref_a_0 *a = __cself->a; // bound by ref
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_47_6nlw9jbn3fb7c8lb1km1rzmm0000gn_T_ZMX_c9e1ad_mi_0,(a->__forwarding->a));
        }
    

    我们是通过先获取block捕获到的a的内存地址对应的value,然后打印出来

    所以我们可以捕获并且修改a的值




    笔者是一个刚入门iOS,对block的原理一直是望而却步。

    这次终于鼓足干劲努力尝试一番,一定有很多的不足,希望大家不吝赐教!

    有任何问题可以留言,或者直接联系QQ:346658618

    希望可以相互学习,一起进步!

    相关文章

      网友评论

          本文标题:iOS-Block变量捕获

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