美文网首页iOS进阶干货分享
你真的能写好一个单例么?

你真的能写好一个单例么?

作者: _小迷糊_997 | 来源:发表于2019-05-10 15:35 被阅读18次

    关注公众号“iOSSir”,看你想看的,给你想要的!

    作者:AlexLee002
    原文地址:https://juejin.im/post/5cb067676fb9a0688360f987

    单例可能是 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 单例模式还需要另外考虑。

    iOSSir公众号技术交流微信群!
    需要进群可以添加公众号助理“kele22558!”

    相关文章

      网友评论

        本文标题:你真的能写好一个单例么?

        本文链接:https://www.haomeiwen.com/subject/oruaoqtx.html