Objective-C 单例应该如何实现 init 方法?
我读了一些关于 Obj-C 中的单例的惊人资源:
- 所以问题: 你的 Objective-C 单例是什么样的?
- 周五问答:单身人士的护理和喂养< /a>
- 苹果文档:创建单例实例,
但这些资源都没有涉及 init
方法概念明确,虽然我仍然是 Obj-C 的新手,但我很困惑应该如何实现它。
到目前为止,我知道在 Obj-C 中不可能将 init
私有化,因为它不提供真正的私有方法...所以用户可以调用 [[MyClass alloc] init]
而不是使用我的[MyClass共享实例]
。
我还有哪些其他选择?我相信我还应该处理我的单例的子类化场景。
I read a couple of amazing resources on singletons in Obj-C:
- SO question: What does your Objective-C singleton look like?
- Friday Q&A: Care and Feeding of Singletons
- 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
嗯,围绕
init
的一种简单方法是不编写一个让它调用默认 NSObject 实现(仅返回self
)的方法。然后,对于您的sharedInstance
函数,定义并调用一个私有函数,该函数在实例化单例时执行类似 init 的工作。 (这可以避免用户意外地重新初始化您的单例。)但是!!! 主要问题是您的用户调用
alloc
代码!为此,我个人推荐Apple的重写allocWithZone:
的路线...这意味着用户仍然会获得您的单例实例,并且他们可以错误地使用它,就好像他们分配了它一样,并安全地释放它一次这个自定义分配在单例上执行保留。 (注意:
alloc
调用allocWithZone:
并且不需要单独重写。)希望有帮助!如果您需要更多信息,请告诉我~
编辑:扩展答案以提供示例和更多详细信息 --
考虑到 Catfish_Man 的答案,创建防弹单例通常并不重要,而只需在标题/文档中编写一些合理的注释并放入
assert
即可。然而,就我而言,我想要一个线程安全的延迟加载单例——也就是说,它直到需要使用时才被分配,而不是在应用程序启动时自动分配。在学习了如何安全地做到这一点后,我想我不妨一直这样做。
编辑#2:我现在使用 GCD 的
dispatch_once(...)
作为线程安全的方法,在应用程序的生命周期内仅分配一次单例对象。请参阅 Apple 文档:GCDdispatch_once。我还仍然添加了 Apple 旧单例示例中的 allocWithZone: 覆盖位,并添加了一个名为 singletonInit 的私有 init 以防止它被意外多次调用: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 returnsself
). Then, for yoursharedInstance
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 overridingallocWithZone:
...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
callsallocWithZone:
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 addallocWithZone:
override bit from Apple's old singleton example, and added a private init namedsingletonInit
to prevent it from accidentally being called multiple times:HSCloudManager
subclassesNSObject
, and does not overrideinit
leaving only the default implementation inNSObject
, 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.诚实地?在我看来,编写防弹单例类的整个时尚似乎有点过分了。如果您真的很担心它,只需在第一次分配之前将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.
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 overrideallocWithZone:
(which is being called byalloc
) to avoid creating more than one instance of your class.要使 init/new 方法对单例类的调用者不可用,您可以在头文件中使用 NS_UNAVAILABLE 宏:
To make the init/new methods unavailable for callers of your singleton class you could use the NS_UNAVAILABLE macro in your header file: