单例的生命周期

iOS app 中单例一经创建,全局共享单例对象,单例的生命周期跟随app生命周期,那它的原理是什么呢?

答:先看单例写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//全局的变量,指的就是Singleton这个对象,静态全局变量,始终指向实例化出的对象
static id instance;
+ (instancetype)shareInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
//创建类
instance = [[self alloc]init];
});
return instance;
}
//创建alloc init 时候创建,会调用allocWithZone ,保证创建出来这个对象是唯一的
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
static dispatch_once_t onceToken;
dispatch_once(&onceToken,^{
//返回自己
instance = [super allocWithZone:zone];
});
return instance;
}
//防止外界拷贝造成多个实例
- (id)copyWithZone:(NSZone *)zone{
return instance;
}

首先使用 static 修饰符,声明了单例对象,这就意味着这个单例对象是在程序的数据区进行存储的,而数据区会跟随 app打开申请 app关闭释放。实现了单例生命周期跟随app的生命周期,这也解释了为什么每次打开app(非后台状态)单例对象就会重新初始化,造成单例上次保存的数据丢失。

那单例对象生命周期是否应该由 MRC/ARC 控制呢?

答案也是否定的,因为无论 ARC 还是 MRC 都是管理用来管理堆上对象的生命周期的,而数据区的单例释放与否是由系统控制的,因此不应受他们控制,需要注意的是,在使用 MRC 时,单例调用 retain 确实会引起计数增加,而调用 release 则会直接崩溃,为了避免单例收到引用计数的干扰造成意想不到的后果,在 MRC 下需要覆盖retain autorelease retainCount release 这几个方法。重写这几个方法是为了单例能在MRC中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#pragma mark - MRC中需要覆盖的方法, ARC与MRC的整合
#if !__has_feature(objc_arc)
- (id)retain {
return self;
}
- (id)autorelease {
return self;
}
- (oneway void)release {
}
- (NSUInteger)retainCount {
return UINT_MAX;
}
#endif