什么是 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 开头