Singletons are probably one of the most familiar design patterns for iOS developers. We also use many singletons 😓 in our project. Recently, I spent more than two days trying to solve the singleton bug in the project, and found that it is really not easy to write a singleton in ObjC!

V1.0

There may be a lot of people unconvinced, singleton, what is difficult, a simple dispatch_once will not solve it! For example:

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

[SingletonClass sharedInstance] [SingletonClass sharedInstance] [SingletonClass sharedInstance] [SingletonClass sharedInstance] [SingletonClass sharedInstance] [SingletonClass sharedInstance] But are there exceptions?

  1. Such asSingletonClassThis class needs to be hosted by other frameworks, so when other frameworks generate instances, they will generally pass for general purpose[[SingletonClass alloc] init]To initialize;
  2. The singleton class in the project is not clearly marked, and the length of the class is similar to other classes, so could some colleagues “misuse”?[[SingletonClass alloc] init]To initialize? It’s not like you had a rule against it.

So here’s the problem: Run the following code:

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

Output result:

The 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: 0x600002A11060Copy the code

As you can see, 1 and 2 are the same, but different from 3 and 4, so this scheme is not perfect.

Disadvantages: There is no guarantee that there should be only one instance of whatever initialization method is used.

V2.0

Long, long ago, in the wild days of iOS, before Swift, Apple called Objective-C “Sweets.” There used to be a sample code for OC singletons on apple’s website (it’s gone now, the link is broken, only swift now, after all, Spears is Swift now). With great effort, I finally found a similar implementation of apple in some other people’s historical articles:

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
Copy the code

This was MRC, and there was no Dispatch_once at that time. Test it after rewriting it to ARC:

The 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: 0x600002AFC430Copy the code

OK! Perfect!

Wait ~~ when it comes to projects, there are still problems. There were instances of singleton inheritance in the original project 😭 (whether singleton inheritance is possible, and when it is used, is another subject of debate ~). Write a subclass inheritance singleton to test it out:

@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]);
Copy the code

The running results are as follows:

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: < 0x60000166CA20 > 2019-04-12 22:10:47.306191+0800 TestProj[6737:7524929] 12: < 0x60000166CA20 >Copy the code

WTF? Dad or dad? Lost son? The reason is that the subclass calls the parent class’s sharedInstance method and returns an instance of the parent class. The subclass is not alloc!

Modify and complete the method for the son:

@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
Copy the code

Continue to run the original test case, crash:

sharedInstance
dispatch_once

Disadvantages: Can’t implement singleton inheritance

V3.0

A closer look at the last version of the crash stack reveals that the problem is the implementation of allocWithZone:! Alter allocWithZone: for both classes to look like this:

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

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

Execute test cases:

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 >Copy the code

🎉🎉🎉 done ~~~

The status of some singletons has been reset. Add some lifecycle methods, plus logging tests… The problem is -init!

Add the following -init method to the parent and child classes:

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

Continue running the test case with the following output:

The 2019-04-12 22:46:44. 697151 + 0800 TestProj [8125-7555154] < SingletonClass: 0x6000014B7830 > Call -[SingletonClass Init] 2019-04-12 22:46:42.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 -[SingletonClass Init] 2019-04-12 22:46:44.697959+0800 TestProj[8125:7555154] 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 > 2019-04-12 22:46:44.698138+0800 TestProj[8125:7555154] 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>Copy the code

As you can see at a glance, whenever you alloc + init a singleton instance, the -init method is executed once, and the state in the singleton is lost.

Disadvantages: There is no guarantee that the initialization method is not reentrant.

V4.0

In our project, we wrote the implementation of a singleton as a template in order to reduce repetitive code, and we simply added this macro to the class implementation to turn the class into a singleton. See my article from a long, long time ago for details.

How do you guarantee that an initialization method is not reentrant? I’ve been thinking about this for a long time, but there seems to be no other way to do this than to add an initialization flag to the -init method. But how do you add tags to -init? I can think of two things:

  1. Replace singleton with Method Swizzle-initMethods. We can add a category to each singleton and implement it in a category+loadMethod (don’t worry about overwriting the main class+load, each category can add its own+loadMethods, and these+loadMethods will be executed), replaced here-init.
  2. Implementation in templates-init, you can add the tag and define a new initialization method-singletonInitIn the-initIs called in. The external singleton class only needs to implement this-singletonInitThat’s it.

After careful consideration, I finally choose plan 2, mainly because the risk of Method Swizzle is not controllable. Plan 2 is conservative but reliable.

Modify a single example -init method implementation:

// The parent class is similar to the subclass
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);
}
Copy the code

Continuing to run the test case results in the following:

The 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 customIC Init 2019-04-13 13:04:35.396231+0800 TestProj[11692:7647465] 01: <SingletonClass customIC Init 0x600002C681D0 > 2019-04-13 13:04:35.396312+0800 TestProj[11692:7647465] 02: <SingletonClass: 0 x600002c681d0 > 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 customIC Init 2019-04-13 13:04:35.396473+0800 TestProj[11692:7647465] 0x600002C63280 > 2019-04-13 13:04:35.396561+0800 TestProj[11692:7647465] 12: <SingletonClassSon: 0x600002C63280 >Copy the code

I don’t think I’m going to do -init again. But the initialization of the subclass is not quite right, right? Because we have now changed the -init method, the actual class initialization is done in -singletoninit, so the subclass initialization must also call the parent class method to ensure full initialization. So we have to call the super method in -singletonInit. [super singletonInit] -singletonInit [super singletonInit] -singletonInit [super singletonInit] You might remember that when you write viewController in Xcode, the -viewwillAppear: and so on, if you don’t say supper call, you get a compile warning that says you have to call super. This takes advantage of the compile property of LLVM, which Apple has encapsulated as a macro: NS_REQUIRES_SUPER. So we continue to add the following code:

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

/// .m
@implementation NSObject (SingletonInit)
- (void)singletonInit {}
@end
Copy the code

Then add [super singletonInit] to -singletonInit for each singleton; Run the test case with the following output:

The 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 customic Init 2019-04-13 13:40:57.294442+0800 TestProj[12932:7675173] 01: <SingletonClass customic Init 2019-04-13 13:40:57.294442+0800 TestProj 0x6000028874F0 > 2019-04-13 13:40:57.294569+0800 TestProj[12932:7675173] 02: <SingletonClass: 0 x6000028874f0 > 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 customIC Init 2019-04-13 13:40:57.294810+0800 TestProj[12932:7675173] 0x600002898240> 2019-04-13 13:40:57.294879+0800 TestProj[12932:7675173] 12: <SingletonClassSon: 0x600002898240>Copy the code

A: Well, it seems that everything has been settled. I see a new concept called weak singleton. Change to wean singleton mode:

// static SingletonClass *instance_SingletonClass = nil;
static __weak SingletonClass *instance_SingletonClass = nil;
Copy the code

Run the following test case:

    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]);
Copy the code

The results are as follows:

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)Copy the code

Once the object is freed, you can’t create any more singletons, you get nil. And the reason is dispatch_once has to be done differently.

Disadvantages: Weak singletons are not supported

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;
}
Copy the code

The output is as follows:

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>
Copy the code

At this point, we have a basically complete ObjC singleton implementation, which we use macros to turn into a template:

  • ALSingletonTemplate.h
    
    #ifndef ALSingletonTemplate_H
    #define ALSingletonTemplate_H
    
    /** * A template code for define a singleton class. * Example:  // .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];  */
    
    ///////////////////////////////////////////////////////////////////////////////////////////////
    /// 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); The \}); \returnsemaphore; \} \ \ +(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; The \}else{ strongRef = __AL_SINGLETON_INSTANCE_FOR_CLASS(cls); }); \} \returnstrongRef; \} \ \ + (id) allocWithZone : (NSZone *) zone {                                                                     \
            if (self == cls.class) {                                                                                     \
                return [selfsharedInstance]; \} \return [superallocWithZone:zone]; \} \ \ -(instancetype) init {                                                                                           \
            static dispatch_semaphore_t semaphore;                                                                       \
            static dispatch_once_t onceToken;                                                                            \
            dispatch_once(&onceToken, ^{                                                                                 \
                semaphore = dispatch_semaphore_create(1); The \}); \ \ 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) {                                                                           \
                    [selfsingletonInit]; \} \}); \return self; \} \ \ -(id) copyWithZone : (nullable NSZone *) zone {                                                                  \
            return self; \} \ -id) mutableCopyWithZone : (nullable NSZone *) zone {                                                           \
            return self; The \}///////////////////////////////////////////////////////////////////////////////////////////////
    #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]; The \};///////////////////////////////////////////////////////////////////////////////////////////////
    #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)); The \}else{\NSAssert(NO.@"[SINGLETON] %s: timeout while waiting to acquire the lock. Deadlock may occured!", __PRETTY_FUNCTION__); The \}#endif // ALSingletonTemplate_H
    Copy the code
  • NSObject+ALSingletonInit.h
    @interface NSObject (ALSingletonInit)
    - (void)singletonInit NS_REQUIRES_SUPER;
    @end
    Copy the code
  • NSObject+ALSingletonInit.m
    #import "NSObject+ALSingletonInit.h"
    
    @implementation NSObject (ALSingletonInit)
    - (void)singletonInit {};
    @end
    Copy the code

To add these files to the project, if a class needs a singleton, simply add two lines to the file:

// .h
@interface MyClass : NSObject
AL_AS_SINGLETON; // <- adds the macro to the header file

/// your code here ...
@end

// .m
@implementation MyClass
AL_SYNTHESIZE_SINGLETON(MyClass); // add the macro to the <-.m file

SingletonInit = singletonInit = singletonInit
/// - (void)singletonInit {
/// /// write the initialization code here, for example
/// _myIvar = xxx;
/ / /}

/// your code here ...
@end
Copy the code

conclusion

To implement a complete singleton with ObjC, note the following:

  • There can only be one instance, regardless of the initialization method.
  • alloc initAtomicity must be maintained, otherwise in a multi-threaded situation a ThreadA will finish alloc, and then another thread will get an instance of the alloc that has not yet been inited, leading to unexpected situations.
  • intIt must be executed only once.
  • [Optional] The inheritance, weak singleton pattern needs another consideration.