Implement Singleton with Objective-C
单例模式的 Objective-C 实现
使用单例的原因是,想要在全局增加一个类的实例所提供的资源的访问点。这里的全局的含义,也说明了从内存上来看,单例是存在于整个程序/app 的生命周期,程序结束时才会被释放。
所以需要做到的事情是:保证所访问的实例即是类唯一的实例。
Objective-C 的实现方式和 GoF 用 C++ 实现的方式类似,也是使用 static
变量:
+ (instance)sharedInstance {
static ClassA * instance = nil;
if (instance == nil) {
instance = [[self alloc] init];
// setup
}
return instance;
}
如果要保证线程安全(单例也是共享资源),则可以在 intance 的 nil
检查周围加上 @synchronized
block 或者 NSLock 实例。
+ (instance)sharedInstance {
static ClassA * instance = nil;
@synchronized(self) {
if (instance == nil) {
instance = [[self alloc] init];
// setup
}
}
return instance;
}
@synchronized 与锁
简单讲的话,可以把 @synchronized
看作语法糖,对于 synchronized 的变量,编译器会在 block 将加锁/解锁等事情做好。
maybe transformed the following:
- (NSString *)myString {
@synchronized(self) {
return [[myString retain] autorelease];
}
}
to:
- (NSString *)myString {
NSString *retval = nil;
pthread_mutex_t *self_mutex = LOOK_UP_MUTEX(self);
pthread_mutex_lock(self_mutex);
retval = [[myString retain] autorelease];
pthread_mutex_unlock(self_mutex);
return retval;
}
从而保证在 @synchronized
的 block 里,能有序使用资源。
内存泄漏问题
单例虽然看上去带来了很多便利,但是网上也有很多比较反对使用。我觉得倒并不是模式本身的错,而是使用方式的问题。因为单例生成后会存在于 app 的整个生命周期中,单例所持有的属性、如果是 strong
的话,那么可能会有内存泄漏的风险。
因为 strong
,单例会持有其属性变量,在访问完成局部的(即其生命周期不应该是整个 app 的)时,其持有的变量本来应该释放掉,却仍然存在于内存中,于是造成了泄漏。
解决这个问题,可以使用 weak
来修饰属性。使用 weak
修饰变量,单例并不会持有这个变量,在变量的生命周期结束时,其会被置为 nil
。
弱单例(Weak Singleton)
这篇文章介绍了一种 weak 单例模式。这种方式,可以改变传统的单例存在于整个 app 的生命周期的情况,当没有对象持有单例时,它就会被释放掉;下一次访问的时候,它又会新建一个新的实例。
使用这种方式要注意两点:
- 其实这种 weak 单例模式是无状态的(因为它会被释放
- 使用的时候,需要一个对象来持有这个单例
弱单例模式实践
使用场景:
开发某个功能时,由于 Web 实现的页面和 Native 的页面间会不断来回跳转,需要考虑清理当前 navigation controller 的栈(避免形成环、并且能 push 和 pop 到产品指定的页面等),转场的动画,以及传递参数等。
对于单个页面来说如何跳转并不应该是它需要关心的事情,这时候需要一个集中式来管理页面交通的角色,自然地想到了 Coordinator。在 App 的生命周期中当然只应该有一个 Coordinator 的实例,因为每个页面也只会有一个实例,因此把它实现为单例。
之前使用 weak
修饰 base controller,解决了内存泄漏的问题。但是,单例的生命周期是和整个 App 的生命周期一致的,所以 Coordinator 并没有被释放掉。
这一周新加的需求需要 Coordinator 保存一些参数,以此来确定跳转的页面的样式、请求接口等。于是想到干脆尝试一下弱单例模式,这样就能安心地给单例加参数,而不用担心内存泄漏的问题。
定义一个弱单例很简单(来自这篇):
+ (id)weakSharedInstance {
static __weak ASingletonClass *instance;
ASingletonClass *strongInstance = instance;
@synchronized(self) {
if (strongInstance == nil) {
strongInstance = [[[self class] alloc] init];
instance = strongInstance;
}
}
return strongInstance;
}
使用的时候,需要一个 controller 来持有这个弱单例。如何选择这个 controller 呢?我觉得可以理解为和这个单例生命周期同步的那一个,比如之前提到的作为“局部小世界”入口的 base controller。
@interface ClassOfControllerA: UIViewController
@property (nonatomic, strong) ASingletonClass *coordinator;
@end
@implementation ClassOfControllerA
- (instancetype)init {
self = [super init];
if (self) {
self.coordinator = [ASingletonClass weakSharedInstance];
self.coordinator.baseController = self;
}
return self;
}
@end