Objective-C单例应该如何实现init方法? [英] How Objective-C singleton should implement init method?

查看:123
本文介绍了Objective-C单例应该如何实现init方法?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  1. SO问题:您的Objective-C单例看起来像什么?
  2. 星期五问答:解决方案

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

但是!!! .主要问题在于您的代码用户正在调用alloc!为此,我个人建议Apple覆盖allocWithZone: ...

的路线

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

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

希望有帮助!让我知道您是否需要更多信息〜

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

考虑到Catfish_Man的回答,通常 创建防弹单例并不重要,而只需在标题/文档中写一些明智的注释并放在assert中即可.

但是,在我的情况下,我想要一个线程安全的延迟加载单例-也就是说,直到需要使用它时才分配它,而不是在应用程序启动时自动分配它.在学习了如何安全地执行该操作之后,我认为我还是可以继续进行下去.

EDIT#2:现在,我将GCD的dispatch_once(...)用于线程安全的方法,即在应用程序的生存期内仅分配一次单例对象.参见 Apple Docs:GCD dispatch_once .我还仍然从Apple的旧单例示例中添加allocWithZone:覆盖位,并添加了一个名为singletonInit的私有初始化,以防止多次意外调用它:

//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:替代中删除保留部分,但保留替代.

I read a couple of amazing resources on singletons in Obj-C:

  1. SO question: What does your Objective-C singleton look like?
  2. Friday Q&A: Care and Feeding of Singletons
  3. Apple docs: Creating a Singleton Instance

but none of these resources addressed init method concept explicitly and while still being a novice to Obj-C I'm confused how should I implement it.

So far I know that having init private is not possible in Obj-C as it does not offer true private methods... so it's possible that user can call [[MyClass alloc] init] instead of using my [MyClass sharedInstance].

What are my other options? I believe I should also handle subclassing scenarios of my singleton.

解决方案

Well, an easy way around the init is to just not write one to have it call the default NSObject implementation (which only returns self). Then, for your sharedInstance function, define and call a private function that performs init-like work when you instantiate your singleton. (This avoids user accidentally re-initializing your singleton.)

However!!! The major problem is with alloc being called by a user of your code! For this, I personally recommend Apple's route of overriding allocWithZone: ...

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

This means the user will still get your singleton instance, and they can mistakenly use as if they allocated it, and safely release it once since this custom alloc performs a retain on the singleton. (Note: alloc calls allocWithZone: and does not need to be separately overridden.)

Hope that helps! Let me know if you want more info~

EDIT: Expanding answer to provide example and more details --

Taking Catfish_Man's answer into consideration, it's often not important to create a bulletproof singleton, and instead just write some sensible comments in your headers/documentation and put in an assert.

However, in my case, I wanted a thread-safe lazy-load singleton--that is, it does not get allocated until it needs to be used, instead of being automatically allocated on app launch. After learning how to do that safely, I figured I may as well go all the way with it.

EDIT#2: I now use GCD's dispatch_once(...) for a thread-safe approach of allocating a singleton object only once for lifetime of an application. See Apple Docs: GCD dispatch_once. I also still add allocWithZone: override bit from Apple's old singleton example, and added a private init named singletonInit to prevent it from accidentally being called multiple times:

//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];
}

HSCloudManager subclasses NSObject, and does not override init leaving only the default implementation in NSObject, which as per Apple's documentation only returns self. This means [[HSCloudManager alloc] init] is the same as [[[HSCloud Manager sharedManager] retain] self], making it safe for both confused users and multi-threaded applications as a lazy-loading singleton.

As for your concern about user's subclassing your singleton, I'd say just comment/document it clearly. Anyone blindly subclassing without reading up on the class is asking for pain!

EDIT#3: For ARC compatibility, just remove the retain portion from the allocWithZone: override, but keep the override.

这篇关于Objective-C单例应该如何实现init方法?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆