【问题标题】:How Objective-C singleton should implement init method?Objective-C 单例应该如何实现 init 方法?
【发布时间】:2011-11-08 14:47:24
【问题描述】:

我阅读了一些关于 Obj-C 中单例的惊人资源:

  1. 所以问题:What does your Objective-C singleton look like?
  2. 周五问答:Care and Feeding of Singletons
  3. 苹果文档:Creating a Singleton Instance

但是这些资源都没有明确地解决init方法概念,虽然我仍然是Obj-C的新手,但我很困惑我应该如何实现它。

到目前为止,我知道 init 在 Obj-C 中是不可能的,因为它不提供真正的私有方法......所以用户可以调用 [[MyClass alloc] init] 而不是使用我的 [MyClass sharedInstance]

我还有哪些其他选择?我相信我还应该处理我的单例的子类化场景。

【问题讨论】:

    标签: objective-c singleton


    【解决方案1】:

    要使单例类的调用者无法使用 init/new 方法,您可以在头文件中使用 NS_UNAVAILABLE 宏:

    - (id)init NS_UNAVAILABLE; 
    + (id)new NS_UNAVAILABLE; 
    
    + (instancetype)sharedInstance;
    

    【讨论】:

      【解决方案2】:

      好吧,解决init 的一种简单方法是不写一个让它调用默认的NSObject 实现(它只返回self)。然后,对于您的sharedInstance 函数,定义并调用一个私有函数,该函数在您实例化单例时执行类似初始化的工作。 (这样可以避免用户意外地重新初始化你的单例。)

      但是!!!主要问题是alloc 被您的代码的用户调用!为此,我个人推荐苹果的路线覆盖allocWithZone: ...

      + (id)allocWithZone:(NSZone *)zone
      {
          return [[self sharedInstance] retain];
      }
      

      这意味着用户仍然会得到你的单例实例,他们可能会错误地使用它,就好像他们分配了它一样,并安全地释放它一次,因为这个自定义分配对单例执行保留。 (注:alloc 调用allocWithZone:,不需要单独覆盖。)

      希望对您有所帮助!如果您想了解更多信息,请告诉我~

      编辑:扩展答案以提供示例和更多详细信息 --

      考虑到 Catfish_Man 的回答,通常创建一个防弹单例并不重要,而只需在您的标题/文档中编写一些合理的 cmets 并放入 assert

      但是,就我而言,我想要一个线程安全的延迟加载单例——也就是说,它在需要使用之前不会被分配,而不是在应用启动时自动分配。在学习了如何安全地做到这一点之后,我想我不妨一路走下去。

      EDIT#2:我现在使用 GCD 的 dispatch_once(...) 线程安全方法,在应用程序的生命周期内只分配一次单例对象。见Apple Docs: GCD dispatch_once。我还添加了 Apple 旧单例示例中的 allocWithZone: 覆盖位,并添加了一个名为 singletonInit 的私​​有 init 以防止它被意外调用多次:

      //Hidden/Private initialization
      -(void)singletonInit 
      {
         //your init code goes here
      }
      
      static HSCloudManager * sharedInstance = nil;   
      
      + (HSCloudManager *) sharedManager {                                   
          static dispatch_once_t dispatchOncePredicate = 0;                  
          dispatch_once(&dispatchOncePredicate, ^{                           
              sharedInstance = [[super allocWithZone:NULL] init];          
              [sharedInstance singletonInit];//Only place you should call singletonInit 
          });                                                                
          return sharedInstance;                                                       
      }
      
      + (id) allocWithZone:(NSZone *)zone {
          //If coder misunderstands this is a singleton, behave properly with  
          // ref count +1 on alloc anyway, and still return singleton!
          return [[HSCloudManager sharedManager] retain];
      }
      

      HSCloudManagerNSObject 的子类,并且不会覆盖init,只保留NSObject 中的默认实现,根据Apple 的文档,它只返回self。这意味着[[HSCloudManager alloc] init][[[HSCloud Manager sharedManager] retain] self] 相同,使其对于困惑的用户和多线程应用程序作为延迟加载单例是安全的。

      至于您对用户子类化您的单身人士的担忧,我想说清楚地评论/记录它。任何盲目的子类化而不阅读课程的人都是要求痛苦!

      EDIT#3:为了 ARC 兼容性,只需从 allocWithZone: 覆盖中删除保留部分,但保留覆盖。

      【讨论】:

      • @yAaak,看起来很公平,但我觉得自己处于无限循环中,需要消化它;)如何确保通过私有方法创建此类单例的过程是线程安全的?其他问题:如果我遵循 Apple 的 allocWithZone 想法,默认 NSObject 的 init 将被调用(只是返回 self 还是其他?)......然后用户再次尝试使用 alloc 和 @ 进行实例化987654346@ 是否会改变我的属性/ivars 初始化和 NSObject 再次获得init 的任何内容?
      • yAak,你是对的:理智的方法你现在建议这样就足够了。我将使用您的代码更多地使用单身人士:) 我认为您的答案非常广泛,并且考虑到了 Catfish_man 的反对意见,所以我接受了它。再次感谢!
      • 我建议 dispatch_once() 而不是 OSAtomic*。不那么挑剔,一样快或更快。
      • 漂亮的图案!谢谢。
      • 如果我们调用 [alloc] init],init 方法会被调用两次(一次使用 sharedManager 方法实现,另一次使用 init itslef)?可以吗?...基本上我看到的是 [[[super allocWithZone:NULL] init] init];
      【解决方案3】:

      说实话?编写防弹单例类的整个时尚对我来说似乎过于夸张了。如果您非常担心它,只需在第一次分配给它之前将 assert(sharedInstance == nil) 粘贴在那里。这样如果有人用错了它就会崩溃,并立即让他们知道他们是个白痴。

      【讨论】:

      • 我部分同意,但我的另一半说:为什么要妥协?但是,我将努力实现我能实现的目标,并在 header/docs 中清除 cmets。
      【解决方案4】:

      init 方法不应该受到影响。在单例类中与普通类中的情况相同。您可能需要覆盖 allocWithZone:(由 alloc 调用)以避免创建多个类的实例。

      【讨论】:

        猜你喜欢
        • 2011-11-05
        • 2011-01-02
        • 2011-11-26
        • 1970-01-01
        • 1970-01-01
        • 2012-01-23
        • 1970-01-01
        • 1970-01-01
        • 2019-06-23
        相关资源
        最近更新 更多