天天看點

你真的能寫好一個單例麼?

關注公衆号“iOSSir”,看你想看的,給你想要的!

單例可能是 iOS 開發者最熟悉設計模式之一了。 我們的項目裡頭也使用了很多單例。 最近為了解決項目中單例的 bug 而花費了兩天多的時間,發現用 ObjC 寫好一個單例真的不容易!

V1.0

可能有很多人不服氣,單例麼, 有什麼難的, 一個簡單的

dispatch_once

不就解決了麼! 比如下邊的代碼:

@implementation SingletonClass
+ (instancetype)sharedInstance {
    static SingletonClass *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}
//...
@end           

可能大部分人的單例都是這麼實作的, 貌似也沒啥問題,通過

[SingletonClass sharedInstance]

擷取到的的确都是同一個執行個體。但是有沒有例外情況呢?

  1. 比如

    SingletonClass

    這個類需要托管到其他架構, 那麼其他架構生成執行個體的時候, 為了通用, 一般都會通過

    [[SingletonClass alloc] init]

    來初始化;
  2. 項目中的單例類又沒有明顯辨別, 長的和其他類差不多, 那麼會不會有某些同僚"誤用"

    [[SingletonClass alloc] init]

    來初始化呢? 畢竟你又沒有規定不讓用。

那麼問題來了, 運作下邊代碼:

NSLog(@"1: %p", [SingletonClass sharedInstance]);
NSLog(@"2: %p", [SingletonClass sharedInstance]);
NSLog(@"3: %p", [[SingletonClass alloc] init]);
NSLog(@"4: %p", [[SingletonClass alloc] init]);           

輸出結果:

2019-04-12 18:44:51.147445+0800 TestProj[92371:7344641] 1: 0x600002a0c360
2019-04-12 18:44:51.147553+0800 TestProj[92371:7344641] 2: 0x600002a0c360
2019-04-12 18:44:51.147630+0800 TestProj[92371:7344641] 3: 0x600002a1e700
2019-04-12 18:44:51.147737+0800 TestProj[92371:7344641] 4: 0x600002a11060           

可以看出, 1和2是一樣的, 但是和3, 4都不一樣, 是以這種方案不完善。

弊端:沒有保證無論用何種初始化方法, 都應該隻有一個執行個體。

V2.0

在很久很久以前, iOS的蠻荒時代, 那時候還沒有 swift, 蘋果還把 Objective-C 叫“小甜甜”。 在蘋果網站上, 曾經有一份OC實作單例的 sample code(現在沒有了,連結失效了, 現在隻有 swift 的, 畢竟現在的小甜甜是 swift)。 費了老大的勁, 終于從一些别人的曆史文章裡邊找到了和當年蘋果差不多的實作:

static SingletonClass *sharedInstance = nil;

@implementation SingletonClass
#pragma mark Singleton Methods
+ (id)sharedInstance {
  @synchronized(self) {
      if(sharedInstance == nil)
          sharedInstance = [[super allocWithZone:NULL] init];
  }
  return sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone {
  return [[self sharedInstance] retain];
}
- (id)copyWithZone:(NSZone *)zone {
  return self;
}
- (id)retain {
  return self;
}
- (unsigned)retainCount {
  return UINT_MAX; //denotes an object that cannot be released
}
- (oneway void)release {
  // never release
}
- (id)autorelease {
  return self;
}
- (void)dealloc {
  // Should never be called, but just here for clarity really.
  [someProperty release];
  [super dealloc];
}
@end           

這個還是 MRC 的, 那時候也還沒有

dispatch_once

。 改寫成 ARC 之後測試看看:

2019-04-12 21:59:16.844126+0800 TestProj[6248:7514391] 1: 0x600002afc430
2019-04-12 21:59:16.844285+0800 TestProj[6248:7514391] 2: 0x600002afc430
2019-04-12 21:59:16.844402+0800 TestProj[6248:7514391] 3: 0x600002afc430
2019-04-12 21:59:16.844499+0800 TestProj[6248:7514391] 4: 0x600002afc430
複制代碼           

OK! 完美!

且慢~~ 在用到項目中的時候, 還是有問題。 原來項目中有單例繼承的情況(關于單例是否可以繼承, 以及什麼場景下用單例繼承, 這是另外一個争論話題~)。 那就寫個子類繼承單例, 測試一下吧:

@interface SingletonClassSon : SingletonClass
@end
@implementation SingletonClassSon
@end

/// test case:
NSLog(@"01: %@", [SingletonClass sharedInstance]);
NSLog(@"02: %@", [[SingletonClass alloc] init]);

NSLog(@"11: %@", [SingletonClassSon sharedInstance]);
NSLog(@"12: %@", [[SingletonClassSon alloc] init]);           

運作結果如下:

2019-04-12 22:10:47.305874+0800 TestProj[6737:7524929] 01: <SingletonClass: 0x60000166ca20>
2019-04-12 22:10:47.306011+0800 TestProj[6737:7524929] 02: <SingletonClass: 0x60000166ca20>
2019-04-12 22:10:47.306110+0800 TestProj[6737:7524929] 11: <SingletonClass: 0x60000166ca20>
2019-04-12 22:10:47.306191+0800 TestProj[6737:7524929] 12: <SingletonClass: 0x60000166ca20>           

WTF?爹還是爹, 兒子不見了? 原因是子類調用的是父類的

sharedInstance

方法, 直接傳回父類的執行個體了, 子類根本沒有被 alloc!

修改一下, 給兒子把方法補全:

@interface SingletonClassSon : SingletonClass
@end
@implementation SingletonClassSon
#pragma mark Singleton Methods
+ (id)sharedInstance {
    static SingletonClassSon *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[super allocWithZone:NULL] init];
    });
    return sharedInstance;
}
+ (id)allocWithZone:(NSZone *)zone {
    return [self sharedInstance];
}
- (id)copyWithZone:(NSZone *)zone {
    return self;
}
@end           

繼續運作原來的 test case, 崩了:

調用棧如下, 很明顯子類的

sharedInstance

方法發生了遞歸調用, 導緻

dispatch_once

死鎖了:[圖檔上傳失敗...(image-153bfc-1557473278527)]

弊端:無法實作單例繼承

V3.0

仔細觀察上個版本的崩潰堆棧, 發現問題所在就是

allocWithZone:

的實作! 把兩個類的

allocWithZone:

修改如下:

/// 父類
+ (id)allocWithZone:(NSZone *)zone {
    if (self == SingletonClass.class) {
        return [self sharedInstance];
    }
    return [super allocWithZone:zone];
}

/// 子類
+ (id)allocWithZone:(NSZone *)zone {
    if (self == SingletonClassSon.class) {
        return [self sharedInstance];
    }
    return [super allocWithZone:zone];
}           

執行測試用例:

2019-04-12 22:46:44.697281+0800 TestProj[8125:7555154] 01: <SingletonClass: 0x6000014b7830>
2019-04-12 22:46:44.697575+0800 TestProj[8125:7555154] 02: <SingletonClass: 0x6000014b7830>
2019-04-12 22:46:44.698047+0800 TestProj[8125:7555154] 11: <SingletonClassSon: 0x6000014b7840>
2019-04-12 22:46:44.698309+0800 TestProj[8125:7555154] 12: <SingletonClassSon: 0x6000014b7840>           

大功告成~~~

放到項目中跑起來, 貌似隐約感覺不對~~~ 有些單例中的狀态怎麼被reset 了? 添加一些生命周期方法, 加上日志測試。。。 原來問題在

-init

上!

分别給父類和子類添加如下

-init

方法:

- (instancetype)init {
    self = [super init];
    NSLog(@"%@ call %s", self, __PRETTY_FUNCTION__);
    return self;
}           

繼續運作測試用例, 輸出如下:

2019-04-12 22:46:44.697151+0800 TestProj[8125:7555154] <SingletonClass: 0x6000014b7830> call -[SingletonClass init]
2019-04-12 22:46:44.697281+0800 TestProj[8125:7555154] 01: <SingletonClass: 0x6000014b7830>
2019-04-12 22:46:44.697398+0800 TestProj[8125:7555154] <SingletonClass: 0x6000014b7830> call -[SingletonClass init]
2019-04-12 22:46:44.697575+0800 TestProj[8125:7555154] 02: <SingletonClass: 0x6000014b7830>
2019-04-12 22:46:44.697881+0800 TestProj[8125:7555154] <SingletonClassSon: 0x6000014b7840> call -[SingletonClass init]
2019-04-12 22:46:44.697959+0800 TestProj[8125:7555154] <SingletonClassSon: 0x6000014b7840> call -[SingletonClassSon init]
2019-04-12 22:46:44.698047+0800 TestProj[8125:7555154] 11: <SingletonClassSon: 0x6000014b7840>
2019-04-12 22:46:44.698138+0800 TestProj[8125:7555154] <SingletonClassSon: 0x6000014b7840> call -[SingletonClass init]
2019-04-12 22:46:44.698213+0800 TestProj[8125:7555154] <SingletonClassSon: 0x6000014b7840> call -[SingletonClassSon init]
2019-04-12 22:46:44.698309+0800 TestProj[8125:7555154] 12: <SingletonClassSon: 0x6000014b7840>           

一眼就能看到, 隻要用

alloc

+

init

的方式擷取單例執行個體,

-init

方法都會被執行一次, 單例中的狀态也就會丢失了~。

弊端:無法保證初始化方法不可重入。

V4.0

我們在項目中, 為了減少重複代碼, 把單例的實作寫成一個模闆, 隻需要把這個宏添加到類實作中, 就能把這個類變成單例。詳情可以參考我很久很久以前的

文章

如何保證初始化方法不可重入呢? 這個問題我想了好久, 貌似除了在

-init

方法中添加初始化标記, 沒有其他辦法了。 但是如何在

-init

中添加标記呢? 我能想到的辦法有倆:

  1. 通過 method swizzle 替換單例的

    -init

    方法。 我們可以給每個單例增加一個 category, 然後在 category 中實作

    +load

    方法(不用擔心會覆寫主類中的

    +load

    , 每個 category 都可以添加自己的

    +load

    方法, 而且這些

    +load

    方法都會被執行), 在這裡替換掉

    -init

  2. 模闆中實作

    -init

    , 就可以增加這個标記了, 然後定義一個新的初始化方法

    -singletonInit

    , 在

    -init

    中調用就可以了。外部單例類隻需要實作這個

    -singletonInit

    就可以了。

經過仔細考慮, 我最後選擇了方案二, 主要是 method swizzle 風險不太可控, 方案二雖然保守, 但是比較可靠。

修改一下單例

-init

方法實作:

// 父類, 子類也類似
static SingletonClass *instance_SingletonClass = nil;
- (instancetype)init {
    static dispatch_semaphore_t semaphore;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        semaphore = dispatch_semaphore_create(1);
    });

    SingletonClass *strongRef = instance_SingletonClass;
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    if (strongRef.class != self.class) {
        self = [super init];
        if (self.class == SingletonClass.class) {
            SEL sel = NSSelectorFromString(@"singletonInit");
            if ([self respondsToSelector:sel]) {
                [self performSelector:sel];
            }
            instance_SingletonClass = self;
        }
    }
    dispatch_semaphore_signal(semaphore);
    return self;
}

- (void)singletonInit {
    NSLog(@"caller: %@; SingletonClass customic init", self);
}
複制代碼           

繼續運作測試用例, 結果如下:

2019-04-13 13:04:35.396087+0800 TestProj[11692:7647465] caller: <SingletonClass: 0x600002c681d0>; SingletonClass customic init
2019-04-13 13:04:35.396231+0800 TestProj[11692:7647465] 01: <SingletonClass: 0x600002c681d0>
2019-04-13 13:04:35.396312+0800 TestProj[11692:7647465] 02: <SingletonClass: 0x600002c681d0>
2019-04-13 13:04:35.396402+0800 TestProj[11692:7647465] caller: <SingletonClassSon: 0x600002c63280>; SingletonClassSon customic init
2019-04-13 13:04:35.396473+0800 TestProj[11692:7647465] 11: <SingletonClassSon: 0x600002c63280>
2019-04-13 13:04:35.396561+0800 TestProj[11692:7647465] 12: <SingletonClassSon: 0x600002c63280>
複制代碼           

這次好像沒問題了, 不會重複執行

-init

方法了。 可是子類的初始化貌似不太對?因為我們現在修改了

-init

方法, 真正的類的初始化是在

-init

裡的

-singletonInit

裡邊進行的, 是以子類的初始化也必須調用父類的方法, 這樣才能保證完全初始化。 是以我們必須在

-singletonInit

中調用 super 方法。 可是問題來了,

-singletonInit

是需要開發者自己實作的, 怎麼保證開發者不會忘記添加

[super singletonInit]

呢? 大家可能會想起, 在 xcode 中寫 viewController 的時候,

-viewWillAppear:

等方法, 如果不寫 supper 調用, 就會出編譯警告, 提示你必須調用 super 方法。 這就是利用了 llvm 的編譯屬性, 蘋果已經把它封裝成一個宏:

NS_REQUIRES_SUPER

。 是以我們繼續添加如下代碼:

/// .h
@interface NSObject (SingletonInit)
- (void)singletonInit NS_REQUIRES_SUPER;
@end

/// .m
@implementation NSObject (SingletonInit)
- (void)singletonInit {}
@end
複制代碼           

然後在每個單例的

-singletonInit

中添加

[super singletonInit];

, 運作測試用例, 輸出如下:

2019-04-13 13:40:57.294312+0800 TestProj[12932:7675173] caller: <SingletonClass: 0x6000028874f0>; SingletonClass customic init
2019-04-13 13:40:57.294442+0800 TestProj[12932:7675173] 01: <SingletonClass: 0x6000028874f0>
2019-04-13 13:40:57.294569+0800 TestProj[12932:7675173] 02: <SingletonClass: 0x6000028874f0>
2019-04-13 13:40:57.294653+0800 TestProj[12932:7675173] caller: <SingletonClassSon: 0x600002898240>; SingletonClass customic init
2019-04-13 13:40:57.294724+0800 TestProj[12932:7675173] caller: <SingletonClassSon: 0x600002898240>; SingletonClassSon customic init
2019-04-13 13:40:57.294810+0800 TestProj[12932:7675173] 11: <SingletonClassSon: 0x600002898240>
2019-04-13 13:40:57.294879+0800 TestProj[12932:7675173] 12: <SingletonClassSon: 0x600002898240>
複制代碼           

事情貌似都解決了, 嗯~~ 好像又看到了一個新概念

weak singleton

。 修改成 wean 單例模式:

// static SingletonClass *instance_SingletonClass = nil;
static __weak SingletonClass *instance_SingletonClass = nil;
複制代碼           

運作下邊的測試用例:

id obj = [SingletonClass sharedInstance];
    NSLog(@"01: %@", obj);
    NSLog(@"02: %@", [[SingletonClass alloc] init]);

    obj = [SingletonClass sharedInstance];
    NSLog(@"11: %@", obj);
    NSLog(@"12: %@", [[SingletonClass alloc] init]);

    obj = nil;
    obj = [SingletonClass sharedInstance];
    NSLog(@"21: %@", obj);
    NSLog(@"22: %@", [[SingletonClass alloc] init]);
複制代碼           

結果如下:

2019-04-14 13:24:21.327596+0800 TestProj[36068:8203530] 01: <SingletonClass: 0x600002c8b2b0>
2019-04-14 13:24:21.327725+0800 TestProj[36068:8203530] 02: <SingletonClass: 0x600002c8b2b0>
2019-04-14 13:24:21.327950+0800 TestProj[36068:8203530] 11: <SingletonClass: 0x600002c8b2b0>
2019-04-14 13:24:21.328037+0800 TestProj[36068:8203530] 12: <SingletonClass: 0x600002c8b2b0>
2019-04-14 13:24:21.328366+0800 TestProj[36068:8203530] 21: (null)
2019-04-14 13:24:21.328617+0800 TestProj[36068:8203530] 22: (null)
複制代碼           

對象被釋放之後, 再也不能繼續建立單例了, 得到的都是

nil

。 原因就是,

dispatch_once

, 得換個方法。

弊端:不支援 weak 單例

V5.0

我們把

+sharedInstance

裡邊的

dispatch_once

換成

dispatch_semaphore

+ (id)sharedInstance {
    __block SingletonClass *strongRef = instance_SingletonClass;
    if (strongRef == nil) {
        static dispatch_semaphore_t lock;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            lock = dispatch_semaphore_create(1);
        });
        dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
        if (instance_SingletonClass == nil) {
            strongRef = [[super allocWithZone:NULL] init];
            instance_SingletonClass = strongRef;
        } else {
            strongRef = instance_SingletonClass;
        }
        dispatch_semaphore_signal(lock);
    }
    return strongRef;
}
複制代碼           

輸出如下:

2019-04-14 13:29:20.280302+0800 TestProj[36272:8208680] 01: <SingletonClass: 0x600003824970>
2019-04-14 13:29:20.280400+0800 TestProj[36272:8208680] 02: <SingletonClass: 0x600003824970>
2019-04-14 13:29:20.280486+0800 TestProj[36272:8208680] 11: <SingletonClass: 0x600003824970>
2019-04-14 13:29:20.280594+0800 TestProj[36272:8208680] 12: <SingletonClass: 0x600003824970>
2019-04-14 13:29:20.280871+0800 TestProj[36272:8208680] 21: <SingletonClass: 0x600003824970>
2019-04-14 13:29:20.281358+0800 TestProj[36272:8208680] 22: <SingletonClass: 0x600003824970>
複制代碼           

至此, 我們得到了一個基本完整ObjC單例實作, 我們用宏把它變成一個模闆:

  • ALSingletonTemplate.h
    #ifndef ALSingletonTemplate_H
    #define ALSingletonTemplate_H
    
    /**
     * A template code for define a singleton class.
     * Example:
        <code>
        // .h file
        @interface SingletionTest : NSObject
        AS_SINGLETON
        @end
    
        // .m file
        @implementation SingletionTest
        SYNTHESIZE_SINGLETON(SingletionTest)
        // IMPORTANT: DO NOT add `-init` in you singleton class!!! you should use `-singletonInit` instead!!!
        // and DONT FORGET to add `[super singletonInit]` in you singletonInit method.
        - (void)singletonInit {
            [super singletonInit];
            // your init code here ...
        }
    
        // your code here ...
        @end
    
        // usage:
        SingletionTest *singleton = [SingletionTest sharedInstance];
        // or: SingletionTest *singleton = [[SingletionTest alloc] init];
        // or: SingletionTest *singleton = [SingletionTest new];
        </code>
     */
    
    ///////////////////////////////////////////////////////////////////////////////////////////////
    /// singleton
    #undef AL_AS_SINGLETON
    #if __has_feature(objc_arc)
        #define AL_AS_SINGLETON             \
            + (instancetype)sharedInstance; \
            + (void)al_destroySingleton;    \
            - (void)al_destroySingleton;
    #else
        #define AL_AS_SINGLETON             \
            + (instancetype)sharedInstance;
    #endif
    
    /// weak singleton; only supports ARC
    #if __has_feature(objc_arc)
        #undef AL_AS_WEAK_SINGLETON
        #define AL_AS_WEAK_SINGLETON  AL_AS_SINGLETON
    #endif
    
    ///////////////////////////////////////////////////////////////////////////////////////////////
    #undef AL_SYNTHESIZE_SINGLETON
    #if __has_feature(objc_arc)
        #undef AL_SYNTHESIZE_WEAK_SINGLETON
        #define AL_SYNTHESIZE_WEAK_SINGLETON(CLS)                               \
            static __weak CLS *__AL_SINGLETON_INSTANCE_FOR_CLASS(CLS) = nil;    \
            __AL_SYNTHESIZE_SINGLETON_ARC(CLS);
    
        #define AL_SYNTHESIZE_SINGLETON(CLS)                                    \
            static CLS *__AL_SINGLETON_INSTANCE_FOR_CLASS(CLS) = nil;           \
            __AL_SYNTHESIZE_SINGLETON_ARC(CLS);
    #else
        #define AL_SYNTHESIZE_SINGLETON(CLS)                           \
            static CLS *__AL_SINGLETON_INSTANCE_FOR_CLASS(CLS) = nil;  \
            __AL_SYNTHESIZE_SINGLETON_MRC(CLS);
    #endif
    
    ///////////////////////////////////////////////////////////////////////////////////////////////
    #undef __AL_SINGLETON_SEMAPHORE_FOR_CLASS
    #define __AL_SINGLETON_SEMAPHORE_FOR_CLASS(cls)   __AL_SINGLETON_MACRO_CONCAT(__al_singleton_semaphore_, cls)
    
    #undef __AL_SYNTHESIZE_SINGLETON_COMMON
    #define __AL_SYNTHESIZE_SINGLETON_COMMON(cls)                                                                        \
        +(dispatch_semaphore_t) __AL_SINGLETON_SEMAPHORE_FOR_CLASS(cls) {                                                \
            static dispatch_semaphore_t semaphore;                                                                       \
            static dispatch_once_t onceToken;                                                                            \
            dispatch_once(&onceToken, ^{                                                                                 \
                semaphore = dispatch_semaphore_create(1);                                                                \
            });                                                                                                          \
            return semaphore;                                                                                            \
        }                                                                                                                \
                                                                                                                         \
        +(instancetype) sharedInstance {                                                                                 \
            if (self != cls.class) {                                                                                     \
                printf(                                                                                                  \
                    "‼️ [SINGLETON] class `%s` invokes `%s` will return the instance of `%s`, which is not the one " \
                    "you expected.\n\n",                                                                                     \
                    NSStringFromClass(self).UTF8String, __PRETTY_FUNCTION__, #cls);                                      \
            }                                                                                                            \
            __block cls *strongRef = __AL_SINGLETON_INSTANCE_FOR_CLASS(cls);                                             \
            if (strongRef == nil) {                                                                                      \
                dispatch_semaphore_t semaphore = [cls __AL_SINGLETON_SEMAPHORE_FOR_CLASS(cls)];                          \
                __AL_SINGLETON_SEMAPHORE_WITH_TIMEOUT(semaphore,                                                         \
                                                      if (__AL_SINGLETON_INSTANCE_FOR_CLASS(cls) == nil) {               \
                                                          strongRef = [[super allocWithZone:NULL] init];                 \
                                                          __AL_SINGLETON_INSTANCE_FOR_CLASS(cls) = strongRef;            \
                                                      } else { strongRef = __AL_SINGLETON_INSTANCE_FOR_CLASS(cls); });   \
            }                                                                                                            \
            return strongRef;                                                                                            \
        }                                                                                                                \
                                                                                                                         \
            + (id) allocWithZone : (NSZone *) zone {                                                                     \
            if (self == cls.class) {                                                                                     \
                return [self sharedInstance];                                                                            \
            }                                                                                                            \
            return [super allocWithZone:zone];                                                                           \
        }                                                                                                                \
                                                                                                                         \
        -(instancetype) init {                                                                                           \
            static dispatch_semaphore_t semaphore;                                                                       \
            static dispatch_once_t onceToken;                                                                            \
            dispatch_once(&onceToken, ^{                                                                                 \
                semaphore = dispatch_semaphore_create(1);                                                                \
            });                                                                                                          \
                                                                                                                         \
            cls *strongRef = __AL_SINGLETON_INSTANCE_FOR_CLASS(cls);                                                     \
            __AL_SINGLETON_SEMAPHORE_WITH_TIMEOUT(semaphore, if (strongRef.class != self.class) {                        \
                self = [super init];                                                                                     \
                if (self.class == cls.class) {                                                                           \
                    [self singletonInit];                                                                                \
                }                                                                                                        \
            });                                                                                                          \
            return self;                                                                                                 \
        }                                                                                                                \
                                                                                                                         \
        -(id) copyWithZone : (nullable NSZone *) zone {                                                                  \
            return self;                                                                                                 \
        }                                                                                                                \
        -(id) mutableCopyWithZone : (nullable NSZone *) zone {                                                           \
            return self;                                                                                                 \
        }
    
    ///////////////////////////////////////////////////////////////////////////////////////////////
    #undef __AL_SYNTHESIZE_SINGLETON_ARC
    #define __AL_SYNTHESIZE_SINGLETON_ARC(cls)                                                        \
        __AL_SYNTHESIZE_SINGLETON_COMMON(cls);                                                        \
        + (void)al_destroySingleton {                                                                 \
            printf("‼️ [SINGLETON] The singleton instance `%s` will be deallocated.\n",           \
                   [self description].UTF8String);                                                    \
            dispatch_semaphore_t lock = [cls __AL_SINGLETON_SEMAPHORE_FOR_CLASS(cls)];                \
            __AL_SINGLETON_SEMAPHORE_WITH_TIMEOUT(lock,                                               \
                __AL_SINGLETON_INSTANCE_FOR_CLASS(cls) = nil;                                         \
            );                                                                                        \
        }                                                                                             \
        -(void) al_destroySingleton {                                                                 \
            [self.class al_destroySingleton];                                                         \
        };
    
    ///////////////////////////////////////////////////////////////////////////////////////////////
    #undef __AL_SYNTHESIZE_SINGLETON_MRC
    #define __AL_SYNTHESIZE_SINGLETON_MRC(cls)              \
        __AL_SYNTHESIZE_SINGLETON_COMMON(cls);              \
                                                            \
        - (instancetype)retain { return self; }             \
        - (oneway void)release{}                            \
        - (instancetype)autorelease {  return self; }       \
        - (NSUInteger)retainCount { return NSUIntegerMax; }
    
    ///////////////////////////////////////////////////////////////////////////////////////////////
    
    #undef __AL_SINGLETON_MACRO_CONCAT_
    #define __AL_SINGLETON_MACRO_CONCAT_(a, b) a##b
    #undef __AL_SINGLETON_MACRO_CONCAT
    #define __AL_SINGLETON_MACRO_CONCAT(a, b) __AL_SINGLETON_MACRO_CONCAT_(a, b)
    
    #undef __AL_SINGLETON_INSTANCE_FOR_CLASS
    #define __AL_SINGLETON_INSTANCE_FOR_CLASS(cls) __AL_SINGLETON_MACRO_CONCAT(__al_singleton_instance_, cls)
    
    ///
    /// execute the code statements `jobStmt` in dispatch_semaphore.
    /// Try to get the semaphore in 10 secods, if failed, that may means a deadlock is occured. and you should check you code.
    /// @note DO NOT return in `jobStmt`, otherwise the samaphore will not be processed correctly.
    ///
    #undef __AL_SINGLETON_SEMAPHORE_WITH_TIMEOUT
    #define __AL_SINGLETON_SEMAPHORE_WITH_TIMEOUT(sema, jobStmt)                                                        \
        if (dispatch_semaphore_wait((sema), dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.f * NSEC_PER_SEC))) == 0) {   \
            jobStmt;                                                                                                    \
            dispatch_semaphore_signal((sema));                                                                          \
        } else {                                                                                                        \
            NSAssert(NO, @"[SINGLETON] %s: timeout while waiting to acquire the lock. Deadlock may occured!", __PRETTY_FUNCTION__); \
        }
    
    #endif // ALSingletonTemplate_H           
  • NSObject+ALSingletonInit.h
    @interface NSObject (ALSingletonInit)
    - (void)singletonInit NS_REQUIRES_SUPER;
    @end           
  • NSObject+ALSingletonInit.m
    #import "NSObject+ALSingletonInit.h"
    
    @implementation NSObject (ALSingletonInit)
    - (void)singletonInit {};
    @end           

把這幾個檔案添加到工程中, 如果某個類需要時單例, 隻需在檔案中簡單的添加兩行就可以:

// .h
@interface MyClass : NSObject
AL_AS_SINGLETON; // <- 頭檔案中加入這個宏

/// your code here ...
@end

// .m
@implementation MyClass
AL_SYNTHESIZE_SINGLETON(MyClass); // <- .m檔案中加入這個宏

/// 需要注意的是, 初始化不能直接用 init 方法, 需要用 singletonInit
/// - (void)singletonInit {
///    /// 初始化代碼寫這裡, 比如
///     _myIvar = xxx;
/// }

/// your code here ...
@end           

總結

要用 ObjC 實作一個完整的單例, 需要注意以下幾點:

  • 不管用何種初始化方式, 都隻能有一個執行個體。
  • alloc

    init

    必須保證“原子性”,否則在多線程情況下就會出現 ThreadA 執行完 alloc, 然後另外一個線程就有可能擷取到的是這個剛 alloc 出來還沒執行 init 的執行個體,導緻意外情況。
  • int

    必須保證隻能執行一次。
  • 【可選】繼承,weak 單例模式還需要另外考慮。

繼續閱讀