美文网首页
Block探索

Block探索

作者: wxflyme | 来源:发表于2018-01-04 13:11 被阅读28次

    随着苹果越来越多的API开始提供block的写法,另外加上block在很多时候写起来确实很方便,因此block越来越受到iOS开发者的青睐。打算在这里记录下自己对block探索的一些成果。

    Block的分类

    在iOS中我们常用的block主要有三类:

      1. __NSStackBlock__  -> //存于栈区;   
      2.__NSGlobalBlock__ -> //存于程序数据区;
      3.__NSMollocBlock__ -> //存于堆区;  
    

    这三个不同类型,存放位置不同的block是怎么管理内存的,这里不做解释。

    Block捕获局部变量

    在block中可以捕获始终类型的数据:全局变量,全局静态变量,局部静态变量,局部变量。这里只讲block对局部变量的捕获。首先我们在MRC环境下写如下测试代码,为了便于后面的说明我们给这段代码叫做A:

    int main(intargc,constchar* argv[]) {
      @autoreleasepool {
        __block NSMutableString* mutableWXKey = [[NSMutableString alloc] initWithFormat:@"user's"];
        NSString* wxKey = [[NSMutableString alloc] initWithFormat:@"name"];
        int test =1;
    
        void(^myStackBlock)(void) = ^(){
    
            [mutableWXKeystringByAppendingString:@"name"];
    
            NSLog(@"我是栈block");
    
            NSLog(@"%d",test);
    
            NSLog(@"%@",wxKey);
    
        };
    
        NSLog(@"myStackBlock ->  %p",myStackBlock);
    
        void(^myGlobalBlock)(void) = ^(){
    
            NSLog(@"我是全局block");
    
            NSLog(@"%d",test);
    
            NSLog(@"%@",wxKey);
    
        };
    
        NSLog(@"myGlobalBlock ->  %p",myGlobalBlock);
    
        void(^myMollocBlock)(void) = [^(){
    
            [mutableWXKeystringByAppendingString:@"name"];
    
            NSLog(@"我是堆block");
    
            NSLog(@"%d",test);
    
            NSLog(@"%@",wxKey);
    
        }copy];
    
        NSLog(@"myMollocBlock ->  %p",myMollocBlock);
    
        myStackBlock();
    
        myGlobalBlock();
    
        myMollocBlock();
    
        NSLog(@"%@",mutableWXKey);
    
      }
      return 0;
    }
    
    然后我们需要利用clang将我们的这段OC代码转换成C++代码,方便我们去研究Block究竟是怎么去捕获局部变量、__block修饰符的原理。在终端中将路劲切换到我们的工程目录下并通过命令:clang -rewrite-objc main.m即可得到我们需要的C++代码。陈宫后如下所示: WX20180104-111033.png

    我们可以用自己喜欢的编辑器打开这个main.cpp文件。在文件中我们可以看到这样一个结构体:

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

    那么这个结构体有什么意义呢?我们在A代码段的NSLog(@"%@",mutableWXKey);这段代码处打上一个断点,然后运行代码,在Xcode的控制台可以看到一些有趣的事情,结果如下图所示:

    WX20180104-110853.png
    我们会发现这个结构成员和我们在控制台上看到的block的成员变量很像,其实block在C++中就是用这样一个结构体来表示的。说了这么多,开始进入正题,赶紧搬好凳子,备好瓜子,准备听我唠叨了,哈哈哈。。。。

    我们回到main.cpp这个文件中,从第99138行开始看,我们会发现几对看起来名称相似的结构体和函数,名称类似这一样

    __main_block_impl_0;__main_block_func_0;__main_block_copy_0;__main_block_dispose_0;__main_block_desc_0

    __main_block_impl_1;__main_block_func_1;__main_block_copy_1;__main_block_dispose_1;__main_block_desc_1

    __main_block_impl_2;__main_block_func_2;__main_block_copy_2;__main_block_dispose_2;__main_block_desc_2;

    在我们的代码段中总共定义了三个block变量,这里恰好出现三对这些东西,是不是有啥关联呢,朋友你很聪明,恭喜你才对了,赏你一个☺。为了方便说明,在这里我们用X代表数字:

    __main_block_impl_X__main_block_desc_X是两个结构体,其他的是函数。在这里我们主要研究__main_block_impl_X__main_block_func_X

    定义block

    在我们的源代码main.m中我们定义了一个wxKey的变量并且没有用__block修饰我们在mian.cpp中可以看到我们定义的block被转换成了这样的:

      void(*myStackBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, test, wxKey, (__Block_byref_mutableWXKey_0 *)&mutableWXKey, 570425344));
    

    一个函数指针。然后这里出现了这个东西:__main_block_impl_0。那么这是什么呢?

    block捕获局部变量

    我们回到__main_block_impl_0这个结构体中,我们会发现,结构体中定义了构造函数并且是以初始化列表的形式提供的。那么不难理解这里就是通过初始化列表的方式构造了一个函数指针,那么我们来看看这个构造函数的传入了哪些参数。总共6个参数其中第3、4、5个参数通过变量名,我们发现是我们的block捕获的三个局部变量的名字,也就可以理解block就是在这里对局部变量进行捕获的。但是这里是不是会有一个疑问?testwxKey这两个变量还可以理解,这个(__Block_byref_mutableWXKey_0 *)&mutableWXKey是什么鬼,这里就涉及到我们要说的__block这修饰符了

    __block修饰符

    上面疑问为什么在捕获mutableWXKey这个变量是会出现不一样的情况,回到main.m文件,我们发现mutableWXKey这个变量用__block这是进行了修饰,而test和wxKey没有,我们知道在使用block时,如果要在block内对从外部捕获到的变量进行修改,需要对外部的局部变量用__block 修饰,在这有两种不同情况,block外的局部变量分为:基础数据类型和引用类型,基础数据类型如果不用__block修饰,在block内对变量进行修改,编译器会报错,但是引用类型不用__block修饰不一定会报错,但是存在的问题的是,当引用类型的变量在block内被修改后,出了block的作用域之后,会发现局部变量的值未改变。

    我们回到(__Block_byref_mutableWXKey_0 *)&mutableWXKey这里。首先我们去看看__Block_byref_mutableWXKey_0这是什么。在main.cpp文件找到这个,发现竟然是一个结构体。惊喜不惊喜,我明明定义的是一个NSMutableString类型,咋就莫名奇妙成了一个结构体。别急,我们来看看这个结构体到底是个啥面目:

      struct __Block_byref_mutableWXKey_0 {
        void *__isa;
        __Block_byref_mutableWXKey_0 *__forwarding;
        int __flags;
        int __size;
        void (*__Block_byref_id_object_copy)(void*, void*);
        void (*__Block_byref_id_object_dispose)(void*);
        NSMutableString *mutableWXKey;
    };
    

    我们发现这个结构体竟然包含一个名叫mutableWXKey的成员变量,而且恰好就是我们要的NSMutableString类型,那么问题来了,为啥要多此一举先弄个结构出来在把这个变量放到结构体中呢?而且这里在传递参数的时候只把这个结构体的地址给传进去,我们知道__block的作用就是让我们在调用block时,能在block内部对捕获到的局部变量进行修改。那么我们先看下block怎么调用的,我给这段代码取个名字叫B,方便后面说明:

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

    看到这个,这时候就有句当讲不当讲了,我感觉clang你是在为难我,这一大串是啥,木办法,只能耐着性子,慢慢看了。

    我们知道__block_impl这是一个结构体,FuncPtr是这个结构体的成员变量,而且还是传说中的万能指针。此时似乎是雾里看花,雾突然渐渐消散起来了,啦啦啦,继续。
    拿到这个指针后我们把这个指针转换成(void (*)(__block_impl *))类型,这是一个函数指针,返回值为void,入参是一个__block_impl类型的结构体指针。貌似看到希望了,激动啊。继续看FuncPtr这个成员变量是啥呢。回到我们的block的定义处,具体看
    __main_block_impl_0这个构造函数,调用这个构造函数的时候我们传入的第一个参数是(void *)__main_block_func_0这是一个函数指针,而在构造函数里面我们将这个指针赋值给了FuncPtr这个成员变量,那么这里的代码段B实际就是调用__main_block_func_0这个函数了,那我们就赶紧去看看这个函数干了啥呢:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_mutableWXKey_0 *mutableWXKey = __cself->mutableWXKey; // bound by ref
    int test = __cself->test; // bound by copy
    NSString *wxKey = __cself->wxKey; // bound by copy
    
            ((NSString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)(mutableWXKey->__forwarding->mutableWXKey), sel_registerName("stringByAppendingString:"), (NSString *)&__NSConstantStringImpl__var_folders_fm_xcy0l9917d5gvhx85v295_k80000gn_T_main_e8ea30_mi_2);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_fm_xcy0l9917d5gvhx85v295_k80000gn_T_main_e8ea30_mi_3);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_fm_xcy0l9917d5gvhx85v295_k80000gn_T_main_e8ea30_mi_4,test);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_fm_xcy0l9917d5gvhx85v295_k80000gn_T_main_e8ea30_mi_5,wxKey);
    }
    

    这个函数的入参是__main_block_impl_0类型的结构体指针,该结构体的定义如下:

    struct __main_block_impl_0 {
        struct __block_impl impl;
        struct __main_block_desc_0* Desc;
        int test;
        NSString *wxKey;
        __Block_byref_mutableWXKey_0 *mutableWXKey; // by ref
        __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _test,         
        NSString *_wxKey, __Block_byref_mutableWXKey_0 *_mutableWXKey, int flags=0) : test(_test), wxKey(_wxKey), mutableWXKey(_mutableWXKey->__forwarding) {
            impl.isa = &_NSConcreteStackBlock;
            impl.Flags = flags;
            impl.FuncPtr = fp;
            Desc = desc;
        }
    };
    

    这个结构体的成员变量mutableWXKey是一个指针类型,既然是拿到了指针,当然就可以修改这个变量的值了,

    关于__block这个修饰符,总而言之,其实早在看block定义的代码时就应该发现区别的:

    void(myStackBlock)(void) = ((void ()())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, test, wxKey, (__Block_byref_mutableWXKey_0 *)&mutableWXKey, 570425344));

    同样都是局部变量,同样都是引用类型,在调用构造函数的时候,未用__block修饰的wxKey直接传入的是值,而用__block修饰的mutableWXKey传入的是地址。

    总结

    由于自身C++知识不足,在查看main.cpp的代码时比较吃力,并且关于结构体的构造函数,初始化列表的理解可能有很多不足,欢迎讨论。

    参考资料

    https://www.cnblogs.com/graphics/archive/2010/07/04/1770900.html

    相关文章

      网友评论

          本文标题:Block探索

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