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

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

1. 引用全局变量的block,能简单说下ta的底层结构吗?
#import 
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 
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 
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 
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 
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 底层结构 变量捕获 类型)