IOS Block 初探

2020-10-22  本文已影响0人  maskerII

一、什么是Block ?
Block 是将函数及其执行上下文封装起来的对象

比如:

- (void)blockTest1 {
   int num = 3;
   int(^myBlock)(int n) = ^int(int n){
       return num *n;
   };
   num = 1;
   myBlock(2);
}
     

将Objective-C代码转换为C\C++代码
指令如下

xcrun  -sdk  iphoneos  clang  -arch  arm64  -rewrite-objc  OC源文件  -o  输出的CPP文件

通过 clang -rewrite-objc SWTestVC.m 命令编译该.m 文件

blockTest1会被编译成如下形式:

static void _I_SWTestVC_blockTest1(SWTestVC * self, SEL _cmd) {
    int num = 3;
    int(*myBlock)(int n) = ((int (*)(int))&__SWTestVC__blockTest1_block_impl_0((void *)__SWTestVC__blockTest1_block_func_0, &__SWTestVC__blockTest1_block_desc_0_DATA, num));

    num = 1;
    ((int (*)(__block_impl *, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 2);
}

SWTestVC 是文件名,blockTest1 是方法名,可以忽略
SWTestVC__blockTest1_block_impl_0 的结构体为

struct __SWTestVC__blockTest1_block_impl_0 {
  struct __block_impl impl;
  struct __SWTestVC__blockTest1_block_desc_0* Desc;
  int num;
  __SWTestVC__blockTest1_block_impl_0(void *fp, struct __SWTestVC__blockTest1_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

_block_impl 结构体为

struct __block_impl {
  void *isa;  // isa指针 
  int Flags;
  int Reserved;
  void *FuncPtr; // 函数指针
};

block 内部有 isa 指针,所以说其本质也是 OC 对象

block内部,也就是 __SWTestVC__blockTest1_block_func_0
如下

static int __SWTestVC__blockTest1_block_func_0(struct __SWTestVC__blockTest1_block_impl_0 *__cself, int n) {
  int num = __cself->num; // bound by copy

        return num *n;
    }

二、Block 变量截获

  1. 局部变量截获 是值截获
    比如:
- (void)blockTest1 {
    int num = 3;
    int(^myBlock)(int n) = ^int(int n){
        return num *n;
    };
    num = 1;
    NSLog(@"%d",myBlock(2));
}

这里的输出是 6 而不是 2,原因就是对局部变量 num 的截获是值截获。 同样,在 block 里如果修改变量 num,也是无效的,甚至编译器会报错

- (void)blockTest2 {
    NSMutableArray * arr = [[NSMutableArray alloc] initWithObjects:@"1",@"2", nil];
    void (^myBlock2)(void) = ^{
        NSLog(@"%@",arr);
    };
    
    [arr addObject:@"3"];
    
    myBlock2();
}

这里打印的是 1,2,3,而不是 1,2
对于局部对象而言,也是截获值,而不是指针,在外部将其置为 nil,对 block 没有影响,而该对象调 用方法会影响~

也就是说 block创建时,会捕获指针arr的值,也就arr所指向的内存的内容,数组的addObject操作,会改变这块内存的内容,但arr = nil,是对指针操作,指针不再指向这块内存,不会对这块内存的内容产生影响~

思考:


- (void)blockTest5 {
    NSMutableArray * arr = [[NSMutableArray alloc] initWithObjects:@"1",@"2", nil];
    void (^myBlock5)(void) = ^{
        NSLog(@"%@",arr); // 局部变量
        [arr addObject:@"4"];
    };
    [arr addObject:@"6"];
    
    NSMutableArray * tempArray = arr;
    arr = [[NSMutableArray alloc] initWithObjects:@"4",@"5", nil];
    [arr addObject:@"3"];
    
    [tempArray addObject:@"7"];
    arr = nil;
    myBlock5();
    
    
}

输出结果 : 1,2,6,7

  1. 局部静态变量截获,是指针截获
- (void)blockTest6{
    static int m = 3;
    int (^myBlock6)(int) = ^ int(int n){
        return  m*n;
    };
    m = 1;
    NSLog(@"%d",myBlock6(2));
}

输出为 2,意味着 m = 1 这里的修改 m 值是有效的,即是指针截获。 同样,在 block 里去修改变量 m,也是有效的。

- (void)blockTest7 {
    static NSArray * arr = nil;
    arr = [[NSArray alloc] initWithObjects:@"1",@"2", nil];
    void (^myBlock7)(void) = ^{
        NSLog(@"blockTest7 %@",arr); // 局部变量
    };
    arr = @[@"1",@"2",@"3"];
//    arr = nil;
    myBlock7();
}

输出 1,2,3
局部对象变量,也是指针截获

  1. 全局变量,静态全局变量截获:不截获,直接取值

我们同样用 clang 编译看下结果


static NSInteger num3 = 300;
NSInteger num4 = 3000;
- (void)blockTest3 {
    NSInteger num = 30;
    static NSInteger num2 = 3;
    __block NSInteger num5 = 30000;
    
    void(^myBlock3)(void) = ^{
        NSLog(@"%zd",num); // 局部变量
        NSLog(@"%zd",num2); // 静态变量
        NSLog(@"%zd",num3); // 静态全局变量
        NSLog(@"%zd",num4); // 全局变量
        NSLog(@"%zd",num5); // __block修饰变量
    };
    
    myBlock3();
}

编译后


struct __SWTestVC__blockTest3_block_impl_0 {
  struct __block_impl impl;
  struct __SWTestVC__blockTest3_block_desc_0* Desc;
  NSInteger num;   // 局部变量
  NSInteger *num2;  // 静态变量
  __Block_byref_num5_0 *num5; // by ref // __block 变量
  __SWTestVC__blockTest3_block_impl_0(void *fp, struct __SWTestVC__blockTest3_block_desc_0 *desc, NSInteger _num, NSInteger *_num2, __Block_byref_num5_0 *_num5, int flags=0) : num(_num), num2(_num2), num5(_num5->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

可以看到局部变量被编译成值形式,而静态变量被编成指针形式,全局变量并未截获。而__block 修饰的变 量也是以指针形式截获的,并且生成了一个新的结构体对象:

struct __Block_byref_num5_0 {
  void *__isa;
__Block_byref_num5_0 *__forwarding;
 int __flags;
 int __size;
 NSInteger num5;
};

该对象有个属性:num5,即我们用__block 修饰的变量。 这里__forwarding 是指向自身的(栈 block)。 一般情况下,如果我们要对 block 截获的局部变量进行赋值操作需添加__block 修饰符,而对全局变量,静态变量是不需要添加__block 修饰符的。

另外,block 里访问 self 或成员变量都会去截获 self。

- (void)blockTest8 {

    self.t = 2;
    self.h = 3;
    void (^myBlock8)(void) = ^ void (void){
        NSLog(@"blockTest8 %d %d",self.t,self.h);
    };
    self.t = 5;
    myBlock8();
   

输出 5,3

三、Block的几种形式

分为全局Block(_NSConcreteGlobalBlock)、栈Block(_NSConcreteStackBlock)、堆Block(_NSConcreteMallocBlock)三种形式,其中栈Block存储在栈(stack)区,堆Block存储在堆(heap)区,全局Block存储在已初始化数据(.data)区

  1. 不使用外部变量的Block是全局Block

比如:

- (void)blockTest11 {
    NSLog(@"%@",[^{NSLog(@"globalBlock");} class]);
}

输出:NSGlobalBlock

  1. 使用外部变量并且未进行 copy 操作的 block 是栈 block

比如:

- (void)blockTest12 {
    int m = 12;
    NSLog(@"%@", [^{NSLog(@"stackBlock %d",m);} class]);
}

输出:NSStackBlock

日常开发常用于这种情况:

- (void)blockTest15 {
    [self testWithBlock:^{
        NSLog(@"blockTest15 %@",self);
    }];
}

- (void)testWithBlock:(void(^)(void))block {
    block();
    NSLog(@"%@",[block class]);
}

输出: NSStackBlock

  1. 对栈Block进行Copy操作,就是堆Block,对全局Block进行Copy操作,还是全局Block

比如 对1中的全局block进行copy操作,即赋值:

- (void)blockTest13 {
    void (^myBlock13)(void) = ^{
        NSLog(@"globalBlock");
    };
    NSLog(@"%@",[myBlock13 class]);
}

输出: NSGlobalBlock
仍是全局Block

而对 2 中的栈 block 进行赋值操作:

- (void)blockTest14 {
    int m = 12;
    void (^myBlock)(void) = ^ {
        NSLog(@"mallocBlock %d",m);
    };
    
    NSLog(@"%@",[myBlock class]);
}

输出:NSMallocBlock
是堆Block

对栈Block Copy后,并不代表栈Block消失了,左边的Block是堆Block,右边被Copy的Block还是栈Block

比如:

- (void)blockTest16 {
    [self testWithBlock2:^{
        NSLog(@"%@",self);
    }];
}

- (void)testWithBlock2:(void(^)(void))block {
    block();
    void (^tempBlock)(void) = block;
    NSLog(@"%@ %@",[tempBlock class],[block class]);
}

输出:NSMallocBlock NSStackBlock

即如果对栈 Block 进行 copy,将会 copy 到堆区,对堆 Block 进行 copy,将会增加引用计数,对全局 Block 进行 copy,因为是已经初始化的,所以什么也不做。

另外,__block 变量在 copy 时,由于__forwarding 的存在,栈上的__forwarding 指针会指向堆上的__forwarding 变量,而堆上的__forwarding 指针指向其自身,所以,如果对__block 的修改,实际上是在修改堆上的__block 变量。

即__forwarding 指针存在的意义就是,无论在任何内存位置,都可以顺利地访问同一个__block 变量。

另外由于 block 捕获的__block 修饰的变量会去持有变量,那么如果用__block 修饰 self,且 self 持有 block,并且 block 内部使用到__block 修饰的 self 时,就会造成多循环引用,即 self 持有 block,block 持 有__block 变量,而__block 变量持有 self,造成内存泄漏。

比如:

- (void)blockTest17 {
    __block typeof(self) weakSelf = self;
    self.testBlock = ^{
        NSLog(@"%@",weakSelf);
        
    };
    self.testBlock();
}

如果要解决这种循环引用,可以主动断开__block 变量对 self 的持有,即在 block 内部使用完 weakself 后, 将其置为 nil,但这种方式有个问题,如果 block 一直不被调用,那么循环引用将一直存在。 所以,我们最好还是用__weak 来修饰 self


- (void)blockTest18 {
    __weak typeof(self) weakSelf = self;
    self.testBlock = ^{
        __strong typeof(self) self = weakSelf;
        NSLog(@"%@",self);
    };
    self.testBlock();
}

上一篇 下一篇

猜你喜欢

热点阅读