iOS Block

205 阅读3分钟

什么是 block

  • block 本质上也是一个 OC 对象,它内部也有个 isa 指针
  • block 是封装了函数调用及函数调用上下文环境的 OC 对象

三种 block 类型

  • 全局区的block:__NSGlobalBlock__
  • 栈区的block:__NSStackBlock__
  • 堆区的block:__NSMallocBlock__

__NSGlobalBlock__

当我们声明一个 block 时,如果这个 block 没有捕获外部变量,那么这个 block 就位于全局区,此时对 NSGlobalBlock 的retain、copy、release都无效,MRC和ARC下都是如此。

/// NSGlobalBlock
NSLog(@"block:%@",^{NSLog(@"block");});
    
/// NSGlobalBlock
void (^block1)(void) = ^{
    NSLog(@"block1");
};
NSLog(@"block1:%@",block1);

以上两个 NSLog 打印出的 block 内存都是在全局区:

block:<__NSGlobalBlock__: 0x1071b6038>
block1:<__NSGlobalBlock__: 0x1071b6058>

__NSStackBlock__

日常开发中,我们遇到的 NSStackBlock 比较少,以下代码中的 block 是 NSStackBlock 的 block。

/// NSStackBlock
int b = 1;
NSLog(@"block3:%@", ^{NSLog(@"b=%d",b);});

__weak void (^block4)(void) = ^{
    NSLog(@"block4 b=%d",b);
};
NSLog(@"block4:%@",block4);

以上两个 NSLog 打印出的 block 内存都是在栈区:

block3:<__NSStackBlock__: 0x7ffeeebfc148>
block4:<__NSStackBlock__: 0x7ffeeebfc118>

日常开发中,我们并不会写 __weak void (^block4)(void) = ^{}; 这样的block。在 ARC环境下,当我们声明并且定义了一个block,并且没有为 block 添加额外的修饰符时,默认是__strong修饰符

__NSMallocBlock__

在 ARC 环境下,__strong(默认) 修饰的 block 只要捕获了外部变量就会位于堆区。实际上,系统帮我们将 __NSStackBlock__转变成了__NSMallocBlock,在这个过程中,系统帮我们完成了 copy 的操作。将栈区的 block 迁移到堆区,延长了 block 的生命周期,栈上的 block,在函数结束退出的时候,该空间就会被回收。

/// NSMallocBlock
int b = 1;
void (^block5)(void) = ^{
    NSLog(@"block5 a:%d",b);
};
NSLog(@"block5:%@",block5);

以上 NSLog 打印出来的 block 内存是在堆区:

block5:<__NSMallocBlock__: 0x600000be6940>

weakSelf和strongSelf

造成循环引用的原因及各种破环方法就不一一举例说明了,接下来重点讲下,在 block 中使用 strongSelf,为什么不会导致循环引用

self.name = @"zhangsan";
NSLog(@"self1: %@ P:%p",self,&self);
__weak typeof(self) weakSelf = self;
NSLog(@"weakSelf: %@ P:%p",weakSelf,&weakSelf);
self.block = ^{
    __strong typeof(self) strongSelf = weakSelf;
    NSLog(@"strongSelf: %@ P:%p",strongSelf,&strongSelf);
    NSLog(@"my name is = %@",strongSelf.name);

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"dispatch_after my name is = %@",strongSelf.name);
    });
};
self.block();

以上代码打印结果:

self1: <ViewController2: 0x7f8d4ee0d810> P:0x7ffeebaeac38
weakSelf: <ViewController2: 0x7f8d4ee0d810> P:0x7ffeebaeac18
strongSelf: <ViewController2: 0x7f8d4ee0d810> P:0x7ffeebaeab58
my name is = zhangsan
dispatch_after my name is = zhangsan

从打印信息中,我们可以看到,self、weakSelf、strongSelf 所指向的内存空间都是同一个,但是 self、weakSelf、strongSelf 都是变量,有自己的内存空间。

为什么在 block 内将对象 __strong 一次不会导致循环引用呢?

  • block 内的 strongSelf 是个临时变量,block 结束时会释放 strongSelf
  • block 内用了 __strong,会使 self 的引用计数器+1,等 block 内的代码执行完毕,结束 block 时,strongSelf 被释放,self 的引用计数器-1

如果 A 页面 push 打开了 B 页面,然后立马返回到 A 页面,上面的代码在 B 页面中,dispatch_after 代码还会被执行吗?

  • 会执行!因为 A 页面创建 B 页面,B 的 retainCount=1。在 block 中 __strong 了一次,会使 self(也就是B)的引用计数器+1,B 的 retainCount=2,返回到 A 页面时,A 不再持有 B 页面,引用计数器-1,B 的 retainCount=1,B 页面不会被释放,直到 block 执行完毕,释放 strongSelf 时,strongSelf 指向的对象(B)引用计数器-1,B 的 retainCount=0,B页面被释放

Tips:如果你仔细观察,会发现全局区的内存地址是 0x1 开头,堆区的内存地址是 0x6 开头,栈区的地址是 0x7 开头