美文网首页Block 探索之路
Block (深入理解)原理探究

Block (深入理解)原理探究

作者: 三月木头 | 来源:发表于2019-12-28 08:11 被阅读0次

    探索场景需求

    1. 局部变量
    2. 全局静态变量
    3. 局部对象(举例NSMutableArray)
    4. 栈对象(NSString *str = @"AAA"; 这种情况,情况个例)
      我们会根据这几种情况,根据打印指针,打印结果,还有探索源码进行分析。

    先说结论

    1. 局部变量首先在heap区开辟空间, 被捕获后copy变量值,另外在stack区开辟一个新的空间存储,block执行后显示的是最开始的数据。
    2. 全局静态变量在static区开辟空间,被捕获不影响其地址,捕获后内部使用仍然是其static区的地址,所以显示变化后的数据。
    3. 局部对象NSMutabblArray,是在heap堆区开辟内存空间,被捕获后还是捕获其原地址指针,所以block执行会根据可变数组变化显示。
    4. 栈对象。同局部变量一样不展开。

    设计相关代码

        //此处是设计的变量、局部对象
        int a = 10;
        static int b = 100;
        NSMutableArray *array1 = [NSMutableArray arrayWithObjects:@"1",@"2", nil];
        
        NSLog(@"a: %p",&a);
        NSLog(@"b: %p",&b);
        NSLog(@"array1: %p",array1);
        self.myblock = ^(SecViewController * aa) {
            NSLog(@"a:%d",a);
            NSLog(@"a: %p",&a);
            NSLog(@"b: %d",b);
            NSLog(@"b: %p",&b);
            NSLog(@"array1:%p",array1);
            
        };
        a = 20;
        b = 200;
        [array1 addObject:@"3"];
        self.myblock(self);
    

    查看一下打印结果:

    
    2019-12-25 19:52:14.569872+0800 BlockTest[9499:776114] a: 0x7ffee83f7fac
    2019-12-25 19:52:14.570121+0800 BlockTest[9499:776114] b: 0x107808828
    2019-12-25 19:52:14.570277+0800 BlockTest[9499:776114] array1: 0x600003598720
    2019-12-25 19:52:32.933938+0800 BlockTest[9499:776114] a:10
    2019-12-25 19:52:33.675075+0800 BlockTest[9499:776114] a: 0x600003598b38
    2019-12-25 19:52:34.803982+0800 BlockTest[9499:776114] b: 200
    2019-12-25 19:52:35.967736+0800 BlockTest[9499:776114] b: 0x107808828
    2019-12-25 19:52:47.685754+0800 BlockTest[9499:776114] array1:0x600003598720
    

    从打印的数据可以验证结论:
    我们局部变量,在block内被捕获,但是通过观察地址,我们发现这个被捕获的局部变量,这是block之前的那个变量进行局部copy而已,地址的指针已经改变。而局部静态变量b一直存在静态区,所以打印的是它变化后的值。对于局部对象数组array1而言,生成时候的地址就是在stack区,block捕获后的地址还是原地址,所以对数组进行操作后,打印出来的是操作后的数据。

    探索不同场景下 block 内部实现

    首先设置研究环境,我们都知道Object-C中,是通过Clang/LLVM对OC代码进行编译,那我们编译一下block所在的.m文件,查看一下其编译成了什么。

    1. 首先我们cd 进入其所在的文件夹。
    2. 通过Clang语音进行编译,然后在对应文件查看相关的C++文件。
    xcrun -sdk iphonesimulator clang -rewrite-objc SecViewController.m
    

    1. 源码解析

    static void _I_SecViewController_viewDidLoad(SecViewController * self, SEL _cmd) {
        ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("SecViewController"))}, sel_registerName("viewDidLoad"));
        int a = 10;
        static int b = 100;
        NSMutableArray *array1 = ((NSMutableArray * _Nonnull (*)(id, SEL, ObjectType _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("arrayWithObjects:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_0, (NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_1, __null);
    
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_2,&a);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_3,&b);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_4,array1);
        ((void (*)(id, SEL, myBlock))(void *)objc_msgSend)((id)self, sel_registerName("setMyblock:"), ((void (*)(SecViewController *))&__SecViewController__viewDidLoad_block_impl_0((void *)__SecViewController__viewDidLoad_block_func_0, &__SecViewController__viewDidLoad_block_desc_0_DATA, a, &b, array1, 570425344)));
        a = 20;
        b = 200;
        ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array1, sel_registerName("addObject:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_10);
        ((myBlock (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("myblock"))(self);
    }
    

    看起来乱糟糕,让我们只提取一下block的信息出来也就是

     self.myblock = ^(SecViewController * aa) {
            NSLog(@"a:%d",a);
            NSLog(@"a: %p",&a);
            NSLog(@"b: %d",b);
            NSLog(@"b: %p",&b);
            NSLog(@"array1:%p",array1);
            
        };
    

    对应的源码如下:

        ((void (*)(id, SEL, myBlock))(void *)objc_msgSend)((id)self, sel_registerName("setMyblock:"), ((void (*)(SecViewController *))&__SecViewController__viewDidLoad_block_impl_0((void *)__SecViewController__viewDidLoad_block_func_0, &__SecViewController__viewDidLoad_block_desc_0_DATA, a, &b, array1, 570425344)));
    
    

    再简化一下,去掉相关的类型编译

       ( (id, SEL, myBlock) objc_msgSend)(self, sel_registerName("setMyblock:"), (&__SecViewController__viewDidLoad_block_impl_0(__SecViewController__viewDidLoad_block_func_0, &__SecViewController__viewDidLoad_block_desc_0_DATA, a, &b, array1, 570425344)));
    
    

    我们看到上面代码。形参id SEL myBlock,我们再抽离一下,只看看myBlock 对应的代码

    &__SecViewController__viewDidLoad_block_impl_0(__SecViewController__viewDidLoad_block_func_0, &__SecViewController__viewDidLoad_block_desc_0_DATA, a, &b, array1, 570425344))
    
    

    我们看到这里的时候需要进去再看看__SecViewController__viewDidLoad_block_impl_0这个到底是什么东东? 根据了解信息猜测,这应该就是myblock在heap区下对应的block信息,看看源码验证一下

    struct __SecViewController__viewDidLoad_block_impl_0 {
      struct __block_impl impl;
      struct __SecViewController__viewDidLoad_block_desc_0* Desc;
      int a;
      int *b;
      NSMutableArray *array1;
      __SecViewController__viewDidLoad_block_impl_0(void *fp, struct __SecViewController__viewDidLoad_block_desc_0 *desc, int _a, int *_b, NSMutableArray *_array1, int flags=0) : a(_a), b(_b), array1(_array1) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    

    果然,如咱们所料,这个就是代码中myBlock下对应的信息。在这里我们可以初步看到,结构体内部拿到的参数中,a是变量,b、 array1是指针。
    此处 fp的实参是什么呢?根据上面myBlock内部代码我们很容易发现是__SecViewController__viewDidLoad_block_func_0,源码如下:

    static void __SecViewController__viewDidLoad_block_func_0(struct __SecViewController__viewDidLoad_block_impl_0 *__cself, SecViewController *aa) {
      int a = __cself->a; // bound by copy
      int *b = __cself->b; // bound by copy
      NSMutableArray *array1 = __cself->array1; // bound by copy
    
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_5,a);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_6,&a);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_7,(*b));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_8,&(*b));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_qw_scdxdg7x5zxfqh31vt91xl9h0000gn_T_SecViewController_83b1d8_mi_9,array1);
    
        }
    

    在这里我们就发现了,此处拿到的a只是一个值传递,b、array1是指针也就是地址传递,所以我们在内部打印时候,只能打印捕获到的a数据,但是通过指针可以获取到改变后的数据b、array1.

    综上:
    我们block内只能操纵局部变量原始数据,可以捕获静态变量、堆对象的最新改变后的数据。


    Git项目代码:下载Block


    相关文章

      网友评论

        本文标题:Block (深入理解)原理探究

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