前言
在阅读该篇文章前,推荐阅读
ios - block原理解读(一)
前情提要
上篇文章理清了block的实现的基本思路,
提到了自动变量中基础类型不能在block内部进行修改。
那么全局变量,全局静态变量,局部静态变量呢?
本文解决问题
- block中引用静态变量/全局变量/全局静态变量
- 被block引用的对象,引用计数为何+=2?
- 循环引用问题、闭环开环的原因
局部静态变量,进行可以修改原值:
int main(int argc, char * argv[]) {
@autoreleasepool {
static int a = 10;
void (^block)(void) = ^{
a++;
NSLog(@"%d",a);
};
block();
return 0;
}
}
这又是为什么呢?
同样,我们看看编译后的C++代码
这里直接放出和前文不一样的片段。
如果你仔细阅读过前文,其他的我就不啰嗦了,
就是从值传递变成了指针传递,
也就是说,block内部将静态变量的地址存储起来,那么用到的时候直接访问其地址就好了。
问:老师♂️♂️,我有个问题,如果block的作用域 > 这个静态变量会输出什么?
答:静态变量存储在静态区,程序结束后由系统释放,所以不存在block的作用域大于静态变量。
针对全局变量,一张截图你就明白了
全局变量和静态变量小科普:存储同样存储在静态区,由系统管理
所以简单来讲,就是系统针对不同类型的变量的作用域和生命周期,做出了相应的处理。
对象变量
在ARC自动引用计数下,当引用计数为0时,对象会被释放。
当block内部访问该对象时,block对其强引用,
首先,通过两段代码,来看看一个问题:
typedef void (^Block)(void);
Block block;
int main(int argc, char * argv[]) {
@autoreleasepool {
TestObject *object = [[TestObject alloc] init];
NSLog(@"引用数 %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
block = ^{
NSLog(@"%@",object);
};
NSLog(@"引用数 %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
return 0;
}
}
输出结果:
2019-02-21 20:54:37.526394+0800 BlockTest[70590:3745629] 引用数 1
2019-02-21 20:54:37.527116+0800 BlockTest[70590:3745629] 引用数 3
typedef void (^Block)(void);
Block block;
int main(int argc, char * argv[]) {
@autoreleasepool {
TestObject *object = [[TestObject alloc] init];
NSLog(@"引用数 %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
{
block = ^{
NSLog(@"%@",object);
};
}
NSLog(@"引用数 %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
return 0;
}
}
输出结果:
2019-02-21 20:55:50.156887+0800 BlockTest[70627:3749548] 引用数 1
2019-02-21 20:55:50.157928+0800 BlockTest[70627:3749548] 引用数 2
先列出MRC和ARC下block的一点区别
MRC时代的block:
只要block引用外部局部变量,block放在栈里面。
ARC时代的block:
只要block引用外部局部变量,block就放在堆里面。
然后,再看一下c++源码:
可以看出:
- 对象类型,多出了copy和dispose函数
- 原有的栈上的结构体指针被copy到了堆,
同时,copy函数内部会将栈对象指向堆对象。
如果你对copy函数有疑问,请查看ios - block原理解读(三)
所以,在block初始化作用域内引用计数+2,
在作用域外栈空间的结构体被回收,引用计数-1,
在block消亡后,引用计数-1。
如果你理解了,看一下代码,并说出结果:
typedef void (^Block)(void);
Block block;
int main(int argc, char * argv[]) {
@autoreleasepool {
TestObject *object = [[TestObject alloc] init];
NSLog(@"引用数 %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
block = ^{
NSLog(@"%@",object);
};
block = nil;
NSLog(@"引用数 %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
return 0;
}
}
答案:1,2
循环引用问题
在ARC大前提下:
- block对对象变量强引用
- 对象引用计数不为0则不会释放
而所谓循环引用是指,多个对象之间相互引用,产生了闭环。
先上代码:
typedef void (^Block)(void);
@interface ViewController ()
@property (nonatomic,copy) Block block;
@property (nonatomic,copy) NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.block = ^{
NSLog(@"%@",self.name);
};
}
说明:
viewController现在持有block
通过上文我们已经知道,
block又强引用了当前的的viewController,
那么在ARC环境下,这两个是不会释放的,造成内存泄露。
解决方法
既然造成了闭环,又在想在block中希望使用viewController,
只能将闭环进行断开。
初步方案,看代码:
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"%@",weakSelf.name);
};
继续根据c++源码分析
首先,__weak的作用是弱引用,不会增加引用计数,
这个具体原理和__strong,__block在后续继续讲解。
然后,可以看到结构体内的属性变成同样是__weak类型的,
不会增加引用计数。
所以,下面代码输出结果是:1,1
int main(int argc, char * argv[]) {
@autoreleasepool {
TestObject *object = [[TestObject alloc] init];
__unsafe_unretained typeof(object) weakObject = object;
NSLog(@"引用数 %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
block = ^{
NSLog(@"%@",weakObject);
};
NSLog(@"引用数 %ld",(long)CFGetRetainCount((__bridge CFTypeRef)object));
return 0;
}
}
所以,上面的闭环状态被我们破坏了,现在仅仅是viewController强引用着block。
安全性
上面的方案,如果block内部执行时间比较长,在执行时,viewController突然被释放了,而block是在堆空间上,并不会被释放,当block内部继续访问viewController,这个时候会出现野指针。
经典解决方案:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@",weakSelf.name);
};
大部分博客只讲到这个解决方案和所谓的短暂的闭环,没有将道理讲明白。
其实,通过上篇文章和上面的解释,我们已经得出了结论。
首先,block引用的外部变量的是__weak修饰的weakSelf对象,
所以block初始化并copy到堆上,不会强引用self。
但是执行block的时候,其实是执行一个静态函数,
在执行的过程中,生成了strongSelf对象,这个时候,产生了闭环。
但是这个strongSelf在栈空间上,在函数执行结束后,strongSelf会被系统回收,此时闭环被打破。
注意:闭环不一定只局限于两个对象,也可能是多个。
最后
以上均为个人研究和理解,如有问题,欢迎评论~
下篇将继续解读,敬请期待!