天天看點

Objective-C中的單例模式(singleton)

以我自己做過的項目來看,單例模式(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