以我自己做過的項目來看,單例模式(singleton)在所有設計模式中應該是用得頻繁的。其中心思想和作用也是在設計模式中最簡單的,在接觸這這個模式後沒有程式員不會喜歡它。好了,廢話不多說,立即看一下在Objective-C語言裡是怎麼實作單例模式。在Objective-C中實作單例和其它語言實作的思想基本一緻。
蘋果官方實作方法
在蘋果的官方文檔庫裡可以找到一篇關于如何建立單例的一些建議(Creating a Singleton Instance)(但時代有點久遠了,裡面的内容也有點過時,這篇文章也就被蘋果遺棄了)
先給裡面給的建議寫出來:
在Objective-C 中要實作一個單例類,至少需要做以下四個步驟:
1、為單例對象實作一個靜态執行個體,并初始化,然後設定成nil;
2、實作一個執行個體構造方法檢查上面聲明的靜态執行個體是否為nil,如果是則建立并傳回一個本類的執行個體;
3、重寫allocWithZone方法,用來保證其他人直接使用alloc和init試圖獲得一個新實力的時候不産生一個新執行個體;
4、适當實作allocWithZone,copyWithZone,release和autorelease。
注: 單例模式在多線程的應用場合下必須小心使用。如果當唯一執行個體尚未建立時,有兩個線程同時調用建立方法,那麼它們同時沒有檢測到唯一執行個體的存在,進而同時各自建立了一個執行個體,這樣就有兩個執行個體被構造出來,進而違反了單例模式中執行個體唯一的原則。
先看看與官方例子差不多的代碼:
/* Singleton.h */
#import <Foundation/Foundation.h>
@interface Singleton : NSObject
+ (Singleton *)sharedInstance;
@end
/* Singleton.m */
#import "Singleton.h"
static Singleton *instance = nil;
@implementation Singleton
+ (Singleton *)sharedInstance {
if (!instance) {
instance = [[super allocWithZone:NULL] init];
}
return instance;
}
+ (id)allocWithZone:(NSZone *)zone {
return [self sharedInstance];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)init {
if (instance) {
return instance;
}
self = [super init];
return self;
}
- (id)retain {
return self;
}
- (oneway void)release {
// Do nothing
}
- (id)autorelease {
return self;
}
- (NSUInteger)retainCount {
return NSUIntegerMax;
}
@end
這段代碼看起來很多,它是建立在非ARC(Automatic Reference Counting)環境下,而且并不是線程安全的。
我們先實作一下ARC環境下的單例模式
1.首先來個簡單的,看如下代碼:
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static Singleton * sharedInstance = nil;
dispatch_once(&onceToken, ^{
sharedInstance = [[Singleton alloc] init];
});
return sharedInstance;
}
說明:
使用了Objective-C中的GCD(Grand Central Dispatch)解決方案,其中利用了dispatch_once的特性。
但是,這種方法還有個缺陷,因為在其它地方可通過alloc和copy任意建立對象,是以把這兩個方法也重寫一下,而且原來實作的方式也要改一下,這樣就有了第二個版本。
2.改良後的版本
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static Singleton * sharedInstance = nil;
dispatch_once(&onceToken, ^{
sharedInstance = [[super allocWithZone:NULL] init];
});
return sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone {
return [self sharedInstance];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
說明:
重寫了allocWithZone和copyWithZone後,調用alloc和copy消息後也不會重複建立新的對象了。如果還要防止調用mutableCopy的話,還要重寫mutableCopyWithZone函數,不過如果預設不實作的話,調用mutableCopy是會有異常的,程式會立即崩潰掉。
但是這個實作也有問題,就是init函數也會有可能被調用多次。是以有了以下版本。
3.再改良後的版本
@implementation Singleton
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static Singleton * sharedInstance = nil;
dispatch_once(&onceToken, ^{
sharedInstance = [[super allocWithZone:NULL] initPrivate];
});
return sharedInstance;
}
+ (instancetype)allocWithZone:(NSZone *)zone {
return [self sharedInstance];
}
- (instancetype)copyWithZone:(NSZone *)zone {
return self;
}
- (instancetype)init {
return self;
}
- (instancetype)initPrivate {
if (self = [super init]) {
// do the stuff
}
return self;
}
@end
根據非ARC版我們可以很容易實作一個ARC版
@implementation Singleton
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static Singleton * sharedInstance = nil;
dispatch_once(&onceToken, ^{
sharedInstance = [[super allocWithZone:NULL] initPrivate];
});
return sharedInstance;
}
+ (instancetype)allocWithZone:(NSZone *)zone {
return [self sharedInstance];
}
- (instancetype)copyWithZone:(NSZone *)zone {
return self;
}
- (instancetype)init {
return self;
}
- (instancetype)retain {
return self;
}
- (oneway void)release {
// Do nothing
}
- (instancetype)autorelease {
return self;
}
- (NSUInteger)retainCount {
return NSUIntegerMax;
}
- (instancetype)initPrivate {
if (self = [super init]) {
// do the stuff
}
return self;
}
@end
然後我們可以用宏封裝一下
用ARC實作的單例和沒用ARC的單例有一些不一樣的地方,要注意。是以如果用到宏的地方可以用以下宏來差別ARC環境下和非ARC環境下的宏:
#if __has_feature(objc_arc)
// ARC
#else
// MRC
#endif
用宏實作:
#define SINGLETON_IN_HEADER(className) +(instancetype)sharedInstance;
// Begin define SINGLETON_IN_IMPLEMENTATION
#if __has_feature(objc_arc)
// ARC version
#define SINGLETON_IN_IMPLEMENTATION(className) \
+ (instancetype)sharedInstance {\
static dispatch_once_t onceToken;\
static className * sharedInstance = nil;\
\
dispatch_once(&onceToken, ^{\
sharedInstance = [[super allocWithZone:NULL] initPrivate];\
});\
\
return sharedInstance;\
}\
\
+ (instancetype)allocWithZone:(NSZone *)zone {\
return [self sharedInstance];\
}\
\
- (instancetype)copyWithZone:(NSZone *)zone {\
return self;\
}\
\
- (instancetype)init {\
return self;\
}
#else
// MRC version
#define SINGLETON_IN_IMPLEMENTATION(className) \
+ (instancetype)sharedInstance {\
static dispatch_once_t onceToken;\
static className * sharedInstance = nil;\
dispatch_once(&onceToken, ^{\
sharedInstance = [[super allocWithZone:NULL] initPrivate];\
});\
return sharedInstance;\
}\
\
+ (instancetype)allocWithZone:(NSZone *)zone {\
return [self sharedInstance];\
}\
\
- (instancetype)copyWithZone:(NSZone *)zone {\
return self;\
}\
\
- (instancetype)init {\
return self;\
}\
\
- (instancetype)retain {\
return self;\
}\
\
- (oneway void)release {\
}\
\
- (instancetype)autorelease {\
return self;\
}\
\
- (NSUInteger)retainCount {\
return NSUIntegerMax;\
}
#endif // End define SINGLETON_IN_IMPLEMENTATION