Objective-C 单例应该如何实现 init 方法?

发布于 2024-12-02 12:58:29 字数 852 浏览 2 评论 0原文

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

  1. 所以问题: 你的 Objective-C 单例是什么样的?
  2. 周五问答:单身人士的护理和喂养< /a>
  3. 苹果文档:创建单例实例

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

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

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

小傻瓜 2024-12-09 12:58:29

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

但是!!! 主要问题是您的用户调用 alloc代码!为此,我个人推荐Apple的重写allocWithZone:的路线...

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

这意味着用户仍然会获得您的单例实例,并且他们可以错误地使用它,就好像他们分配了它一样,并安全地释放它一次这个自定义分配在单例上执行保留。 (注意:alloc 调用 allocWithZone: 并且不需要单独重写。)

希望有帮助!如果您需要更多信息,请告诉我~

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

考虑到 Catfish_Man 的答案,创建防弹单例通常并不重要,而只需在标题/文档中编写一些合理的注释并放入 assert 即可。

然而,就我而言,我想要一个线程安全的延迟加载单例——也就是说,它直到需要使用时才被分配,而不是在应用程序启动时自动分配。在学习了如何安全地做到这一点后,我想我不妨一直这样做。

编辑#2:我现在使用 GCD 的 dispatch_once(...) 作为线程安全的方法,在应用程序的生命周期内仅分配一次单例对象。请参阅 Apple 文档:GCDdispatch_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];
}

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

至于您对用户对您的单例进行子类化的担忧,我只想说清楚地评论/记录它。任何人在没有仔细阅读课程内容的情况下盲目地进行子类化都是自找痛苦!

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

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.

浅语花开 2024-12-09 12:58:29

诚实地?在我看来,编写防弹单例类的整个时尚似乎有点过分了。如果您真的很担心它,只需在第一次分配之前将assert(sharedInstance == nil) 粘贴在那里即可。这样,如果有人使用错误,它就会崩溃,立即让他们知道自己是个白痴。

Honestly? The whole fad of writing bulletproof singleton classes seems pretty overblown to me. If you're seriously that concerned about it, just stick assert(sharedInstance == nil) in there before you assign to it the first time. That way it'll crash if someone uses it wrong, promptly letting them know they're an idiot.

断肠人 2024-12-09 12:58:29

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

The init method should not be affected. It will be the same in a singleton class as in a regular class. You may want to override allocWithZone: (which is being called by alloc) to avoid creating more than one instance of your class.

老旧海报 2024-12-09 12:58:29

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

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

+ (instancetype)sharedInstance;

To make the init/new methods unavailable for callers of your singleton class you could use the NS_UNAVAILABLE macro in your header file:

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

+ (instancetype)sharedInstance;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文