I read a couple of amazing resources on singletons in Obj-C:
我在object - c中读到了一些关于单身人士的令人惊叹的资源:
- SO question: What does your Objective-C singleton look like?
- 问:你的Objective-C单例对象是什么样子的?
- 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.
但是这些资源中没有一个明确地讨论了init方法的概念,而且我还是object - c的新手,我不知道如何实现它。
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]
.
到目前为止,我知道在object - c中不可能有init private,因为它不提供真正的private方法……所以用户可以调用[[myalloc] init]而不是使用[MyClass sharedInstance]。
What are my other options? I believe I should also handle subclassing scenarios of my singleton.
我还有其他的选择吗?我认为我还应该处理单例的子类化场景。
4 个解决方案
#1
25
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.)
init的一个简单方法是不写一个让它调用默认的NSObject实现(它只返回self)。然后,对于您的sharedInstance函数,定义并调用一个在实例化您的单例时执行类init工作的私有函数。(这避免了用户意外地重新初始化您的单例对象。)
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:
...
不过! ! !主要的问题是alloc被你的代码的用户调用!为此,我个人推荐苹果的路线:……
+ (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.)
这意味着用户仍然会得到您的单例实例,并且他们可以错误地使用它,就好像他们分配了它一样,并且安全地释放它一次,因为这个自定义alloc在单例中执行了retain。(注意:alloc调用allocWithZone:不需要单独重写。)
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
.
考虑到Catfish_Man的答案,创建一个防弹单例通常并不重要,相反,只需在头部/文档中编写一些合理的注释并放入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:
编辑#2:我现在使用GCD的dispatch_once(…)作为线程安全的方法,在应用程序的生命周期中只分配一次单例对象。见Apple Docs: GCD dispatch_once。我还添加了allocWithZone: override bit,来自苹果的旧单例,并添加了一个名为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
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.
HSCloudManager子类NSObject,并且不覆盖init,只在NSObject中保留默认实现,根据苹果的文档,它只返回self。这意味着[[[HSCloudManager alloc] init]与[[[[[[HSCloud Manager sharedManager] retain] self]是相同的,这使它作为一个延迟加载的单例应用程序对混乱的用户和多线程应用程序都是安全的。
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.
编辑#3:对于ARC兼容性,只需从allocWithZone删除保留部分:覆盖,但保留覆盖。
#2
3
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.
诚实?编写防弹单例类的整个时尚在我看来似乎有些夸张。如果您非常关心它,那么在第一次分配它之前,只需在其中插入assert(sharedInstance = nil)。这样的话,如果有人用错了它,它就会崩溃,立即让他们知道他们是白痴。
#3
2
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.
不应该影响init方法。它在单例类中与在常规类中是相同的。您可能希望重写allocWithZone:(由alloc调用)以避免创建多个类实例。
#4
0
To make the init/new methods unavailable for callers of your singleton class you could use the NS_UNAVAILABLE macro in your header file:
要使init/new方法对单例类的调用者不可用,您可以在头文件中使用NS_UNAVAILABLE宏:
- (id)init NS_UNAVAILABLE;
+ (id)new NS_UNAVAILABLE;
+ (instancetype)sharedInstance;
#1
25
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.)
init的一个简单方法是不写一个让它调用默认的NSObject实现(它只返回self)。然后,对于您的sharedInstance函数,定义并调用一个在实例化您的单例时执行类init工作的私有函数。(这避免了用户意外地重新初始化您的单例对象。)
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:
...
不过! ! !主要的问题是alloc被你的代码的用户调用!为此,我个人推荐苹果的路线:……
+ (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.)
这意味着用户仍然会得到您的单例实例,并且他们可以错误地使用它,就好像他们分配了它一样,并且安全地释放它一次,因为这个自定义alloc在单例中执行了retain。(注意:alloc调用allocWithZone:不需要单独重写。)
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
.
考虑到Catfish_Man的答案,创建一个防弹单例通常并不重要,相反,只需在头部/文档中编写一些合理的注释并放入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:
编辑#2:我现在使用GCD的dispatch_once(…)作为线程安全的方法,在应用程序的生命周期中只分配一次单例对象。见Apple Docs: GCD dispatch_once。我还添加了allocWithZone: override bit,来自苹果的旧单例,并添加了一个名为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
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.
HSCloudManager子类NSObject,并且不覆盖init,只在NSObject中保留默认实现,根据苹果的文档,它只返回self。这意味着[[[HSCloudManager alloc] init]与[[[[[[HSCloud Manager sharedManager] retain] self]是相同的,这使它作为一个延迟加载的单例应用程序对混乱的用户和多线程应用程序都是安全的。
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.
编辑#3:对于ARC兼容性,只需从allocWithZone删除保留部分:覆盖,但保留覆盖。
#2
3
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.
诚实?编写防弹单例类的整个时尚在我看来似乎有些夸张。如果您非常关心它,那么在第一次分配它之前,只需在其中插入assert(sharedInstance = nil)。这样的话,如果有人用错了它,它就会崩溃,立即让他们知道他们是白痴。
#3
2
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.
不应该影响init方法。它在单例类中与在常规类中是相同的。您可能希望重写allocWithZone:(由alloc调用)以避免创建多个类实例。
#4
0
To make the init/new methods unavailable for callers of your singleton class you could use the NS_UNAVAILABLE macro in your header file:
要使init/new方法对单例类的调用者不可用,您可以在头文件中使用NS_UNAVAILABLE宏:
- (id)init NS_UNAVAILABLE;
+ (id)new NS_UNAVAILABLE;
+ (instancetype)sharedInstance;