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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章