美文网首页iOS 开发 Objective-C
iOS 底层 day08 block 底层结构 变量捕获 类型

iOS 底层 day08 block 底层结构 变量捕获 类型

作者: 望穿秋水小作坊 | 来源:发表于2020-08-30 12:12 被阅读0次

    一、block 引用外部变量时,对外部变量的捕获(capture)情况

    1. 引用全局变量block,能简单说下ta的底层结构吗?
    #import <Foundation/Foundation.h>
    static int age = 10;
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            void (^lspBlock)(void) = ^{
                NSLog(@"我是简单的block:%d",age);
            };
            lspBlock();
        }
        return 0;
    }
    
    • 使用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 将代码转换成 C++代码,有助于我们观察代码的本质
    • 转换获得如下代码
    block中引用了全局变量
    • 从上图,我们可以获得一些信息:
    • ① block内部有 isa 指针,说明 Block 是一个 OC类
    • ② block 类中存储着需要运行的函数指针FuncPtr ,以及对 block的描述信息 __main_block_desc_0
    • __mian_block_impl_0 结构体中没有 age 的信息,说明block没有对全局变量 age 进行捕获
    2. 引用局部变量的block,能简单说下ta的底层结构吗?
    #import <Foundation/Foundation.h>
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            static int age = 10;
            void (^lspBlock)(void) = ^{
                NSLog(@"我是简单的block:%d",age);
            };
            lspBlock();
        }
        return 0;
    }
    
    
    • 转换获得如下代码
    引用局部变量的block
    • 从转换后的代码,我们可以清晰的看到,对于静态局部变量,1block会采取捕获指针的方式,拿到&age的地址值,赋值给__main_block_impl_0` 结构体对象
    3. 引用局部变量的block,能简单说下ta的底层结构吗?
    #import <Foundation/Foundation.h>
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int age = 10;
            void (^lspBlock)(void) = ^{
                NSLog(@"我是简单的block:%d",age);
            };
            lspBlock();
        }
        return 0;
    }
    
    • 转换获得如下代码
    引用局部变量的block
    • 我们可以发现,__main_block_impl_0 中传递的是 age 这个值,说明block捕获局部变量时,是值捕获。
    4. 接下来我们总结一下 block引用外部变量,在各种情况下的捕获情况
    捕获机制总结-牢记
    5. 明白了 block 捕获原理后,我们来强化一下,请问下面代码的输出是多少?
    #import <Foundation/Foundation.h>
    int globalAge = 10;
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int age = 20;
            static int staticAge = 30;
            void (^lspBlock1)(void) = ^{
                NSLog(@"我是简单的block:%d",globalAge);
            };
            void (^lspBlock2)(void) = ^{
                NSLog(@"我是简单的block:%d",age);
            };
            void (^lspBlock3)(void) = ^{
                NSLog(@"我是简单的block:%d",staticAge);
            };
            
            globalAge++;
            age++;
            staticAge++;
            
            lspBlock1();
            lspBlock2();
            lspBlock3();
        }
        return 0;
    }
    
    • 输出如下,你答对了吗?
    Demo[6041:138667] 我是简单的block:11
    Demo[6041:138667] 我是简单的block:20
    Demo[6041:138667] 我是简单的block:31
    
    6.请问下面的 block 有捕获变量吗?捕获的是谁?
    #import "Person.h"
    @interface Person ()
    @property(nonatomic, assign) int age;
    @end
    
    @implementation Person
    - (void)lspTest {
        void (^block)(void) = ^{
            NSLog(@"%i", _age);
        };
        block();
    }
    @end
    
    • _age 实际上是 self->age,而 self 是一个局部实例变量,对于局部变量,block 会采取值捕获 self
    • 还有一个疑惑,思考为什么我们在类中调用方法,可以直接使用 self_cmd 这两个变量
    • 这是因为OC底层替我们在方法中传递了这两个参数,比如上述代码转换后
    static void _I_Person_lspTest(Person * self, SEL _cmd) {
        void (*block)(void) = ((void (*)())&__Person__lspTest_block_impl_0((void *)__Person__lspTest_block_func_0, &__Person__lspTest_block_desc_0_DATA, self, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    

    二、block 的类型

    1. 除了 block 有 isa 指针这个方面来说,我们还可以从哪方面证明 block 是一个 OC 对象?
    • 我们还可以在MRC下通过打印 block 的 class 和 superclass来说明;
    #import <Foundation/Foundation.h>
    int globalAge = 10;
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int age = 20;
            static int staticAge = 30;
            void (^lspBlock1)(void) = ^{
                NSLog(@"我是简单的block:%d",globalAge);
            };
            void (^lspBlock2)(void) = ^{
                NSLog(@"我是简单的block:%d",age);
            };
            void (^lspBlock3)(void) = ^{
                NSLog(@"我是简单的block:%d",staticAge);
            };
            
            void (^lspBlock4)(void) = [lspBlock2 copy];
            
            NSLog(@"lspBlock1: %@, %@, %@, %@",[lspBlock1 class],
                  [lspBlock1 superclass],
                  [[lspBlock1 superclass] superclass],
                  [[[lspBlock1 superclass] superclass] superclass]);
            
            
            NSLog(@"lspBlock2: %@, %@, %@, %@",[lspBlock2 class],
                  [lspBlock2 superclass],
                  [[lspBlock2 superclass] superclass],
                  [[[lspBlock2 superclass] superclass] superclass]);
            
            
            NSLog(@"lspBlock3: %@, %@, %@, %@",[lspBlock3 class],
                  [lspBlock3 superclass],
                  [[lspBlock3 superclass] superclass],
                  [[[lspBlock3 superclass] superclass] superclass]);
            
            NSLog(@"lspBlock4: %@, %@, %@, %@",[lspBlock4 class],
                  [lspBlock4 superclass],
                  [[lspBlock4 superclass] superclass],
                  [[[lspBlock4 superclass] superclass] superclass]);
            
        }
        return 0;
    }
    
    
    • 思考上述代码的输出:
    Demo[6201:154701] lspBlock1: __NSGlobalBlock__, __NSGlobalBlock, NSBlock, NSObject
    Demo[6201:154701] lspBlock2: __NSStackBlock__, __NSStackBlock, NSBlock, NSObject
    Demo[6201:154701] lspBlock3: __NSGlobalBlock__, __NSGlobalBlock, NSBlock, NSObject
    Demo[6201:154701] lspBlock4: __NSMallocBlock__, __NSMallocBlock, NSBlock, NSObject
    
    • 我们发现block最终继承于 NSObject ,再次证明 block 底层就是OC对象
    • 我们还发现,我们打印的block中主要有三种类型 __NSGlobalBlock__NSStackBlock__NSMallocBlock
    • 还发现,无论哪种类型的block都继承于 NSBlock
    2. 既然 block 有三种类型,你能从它们的名字,区分出运行时,它们分别放在内存的哪一段吗?
    • __NSGlobalBlock 放在数据区,特点是:由系统管理,运行过程不会释放
    • __NSStackBlock 放在栈区,特点是:系统自动分配内存,也会自动释放内存
    • __NSMallocBlock放在堆区,特点是:需要程序员自己管理内存
    3. 既然 block 有三种类型,那对它们进行 copy 操作会有什么效果呢?
    copy 操作-牢记
    4. 你可能会遇到的疑惑,如果将 1 中的代码,转换成 c++ 代码
    • 你会获得如下代码
    struct __main_block_impl_1 {
      struct __block_impl impl;
      struct __main_block_desc_1* Desc;
      int age;
      __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _age, int flags=0) : age(_age) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    • 我们重点注意这句话 impl.isa = &_NSConcreteStackBlock; ,我们发现无论哪种 block,C++代码的都会是这句代码
    • 这句话的含义就是将 _NSConcreteStackBlock 这个类对象赋值给 isa 指针
    • 也就说明,从C++代码层面,这些block都是 _NSConcreteStackBlock ,怎么回事呢?
    • 当我们遇到这种情况,一切以运行时的情况为准,因为运行时的结果,才是我们真正的最终结果

    相关文章

      网友评论

        本文标题:iOS 底层 day08 block 底层结构 变量捕获 类型

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