IOS进阶-理解OC中block (二)

上文中介绍了闭包及block的关系,下面结合具体场景再深入探究。

应用场景

代码如下:

NSInteger globalVar = 1;
static NSInteger globalStaticVar = 1;

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here
        NSInteger var = 1;
        static NSInteger staticVar = 1;
        __block NSInteger blockVar = 1;
        NSMutableArray *arr = [NSMutableArray arrayWithArray:@[@1,@2,@3]];
        void (^block)(void) = ^(void) {
            NSLog(@"局部变量: %ld", var);
            NSLog(@"静态变量: %ld", staticVar);
            NSLog(@"全局变量: %ld", globalVar);
            NSLog(@"全局静态变量: %ld", globalStaticVar);
            NSLog(@"block修饰变量: %ld", blockVar);
            [arr addObject:@5];
            NSLog(@"数组: %@", arr);
            globalVar = 3;
        };
        
        var = 2;
        staticVar = 2;
        globalVar = 2;
        globalStaticVar = 2;
        blockVar = 2;
        [arr addObject:@4];
        arr = nil;
        
        block();
        
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

控制台打印

2021-04-21 14:01:55.859015+0800 OCPP[3452:192737] 局部变量: 1
2021-04-21 14:01:55.859754+0800 OCPP[3452:192737] 静态变量: 2
2021-04-21 14:01:55.859880+0800 OCPP[3452:192737] 全局变量: 2
2021-04-21 14:01:55.860001+0800 OCPP[3452:192737] 全局静态变量: 2
2021-04-21 14:01:55.860101+0800 OCPP[3452:192737] block修饰变量: 2
2021-04-21 14:01:55.860419+0800 OCPP[3452:192737] 数组: (
    1,
    2,
    3,
    4,
    5
)

OC文件底层编译命令: xcrun -sdk iphonesimulator clang -rewrite-objc main.m。生成.cpp 文件

NSInteger globalVar = 1;
static NSInteger globalStaticVar = 1;

struct __Block_byref_blockVar_0 {
  void *__isa;
__Block_byref_blockVar_0 *__forwarding;
 int __flags;
 int __size;
 NSInteger blockVar;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSInteger var;
  NSInteger *staticVar;
  NSMutableArray *arr;
  __Block_byref_blockVar_0 *blockVar; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSInteger _var, NSInteger *_staticVar, NSMutableArray *_arr, __Block_byref_blockVar_0 *_blockVar, int flags=0) : var(_var), staticVar(_staticVar), arr(_arr), blockVar(_blockVar->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__main_block_impl_0中__block_impl我们点击去可以看到有个isa指针。所以block本质上就是个对象,一个将定义的函数以及关联的上下文封装起来的一个对象。

我们在block中引用的num,staticNum,blockNum,在这里都被定义成了内部属性.但是并没有发现globalNum和globalStaticNum

num和staticNum还是保留其原来的NSInteger类型,但是blockNum使用的是``__Block_byref_blockNum_0`类型.

结论如下:

变量类型 block处理方式
局部变量 值捕获,block块外部改变值不会对block内部产生影响
静态变量 指针捕获,block块外部改变值会对block内部产生影响
全局变量/全局静态变量 不捕获,直接取值
使用__block关键字修饰 是指针捕获,但是实现不同于静态变量,它会生成一个新的结构体对象。

block形式

block有三种形式,全局block(NSGlobalBlock),栈block(NSStackBlock),堆block(NSMallocBlock)

  • 全局block存储在初始化data区
  • 栈block存储在栈(stack)区
  • 堆block存储在堆(heap)区
    NSLog(@"%@", [^(void){
        
    } class]); // 为引用外部变量

    NSLog(@"%@", [^(void){
        NSInteger num = globalStaticNum;
    } class]); // 引用全局静态变量

    NSLog(@"%@", [^(void){
        NSInteger num = globalNum;
    } class]); // 引用全局变量

    static NSInteger temp = 1;
    NSLog(@"%@", [^(void){
        NSInteger num = temp;
    } class]); // 静态变量

    NSInteger number = 1;
    NSLog(@"%@", [^(void){
        NSInteger num = number;
    } class]); // 局部变量

    NSLog(@"%@", [^(void){
        NSInteger num = self.num;
    } class]); // 局部变量

    NSLog(@"%@", [^(void){
        NSInteger num = self.numObj;
    } class]); // 局部变量

        self.block = ^(b1)(void) {
      
    };
        NSLog(@"%@",self.block); // copy全局block

        self.block = ^(b1)(void) {
      id n = self.numObj;
    };
        NSLog(@"%@",self.block); // copy栈block
    
/*
控制台打印如下:
__NSGlobalBlock__
__NSGlobalBlock__
__NSGlobalBlock__
__NSGlobalBlock__
__NSStackBlock__
__NSStackBlock__
__NSStackBlock__
__NSGlobalBlock__
__NSMallocBlock__
*/

结论如下:

使用方式 结果
不使用外部变量 全局block
对全局block进行copy操作 全局block
使用外部变量,变量为全局变量 全局block
使用外部变量,变量非全局变量 栈block
对栈block进行copy操作 堆block
对堆block进行copy操作 不进行操作,引用计数+1

__block关键字

上文提到,使用关键字修饰的变量,在block中进行指针捕获后并没有定义成原数据类型的指针,而是生成了一个新的结构体对象__Block_byref_blockNum_0:

struct __Block_byref_blockVar_0 {
  void *__isa;
__Block_byref_blockVar_0 *__forwarding;
 int __flags;
 int __size;
 NSInteger blockVar;
};
  • 可以发现__Block_byref_blockNum_0在内部才持有了定义的变量
  • 还有一个指向同类型的指针__forwarding

关于forwarding指针:

  • forwarding是一个指向自身的指针, 当栈block发生copy操作后,会指向其拷贝的堆block。
  • 所以对block中持有的变量修改,其实修改的是最终指向的那个block。这样保证了访问的值是同样了.

你可能感兴趣的:(IOS进阶-理解OC中block (二))