In our app at work we use singletons in a few places. But when you search google for you get mixed answers for the right way to implement it.
I became curious as to what the proper way to accomplish this is, and I did some searching. I eventually found the solution and explanation I was searching for in addition to some really good reasons not to use singletons at all. So I’ll discuss all of that right here.
#Method 1. @synchronize When I was doing my original search this seemed to be the most popular answer. This is the way it was implemented in many places in the app.
header file
#import <Foundation/Foundation.h>
#import <FacebookSDK/FacebookSDK.h>
@interface INTUser : NSObject
@property (nonatomic) NSString *facebookId;
@property (nonatomic, readonly) NSString *facebookBirthday;
@property (nonatomic, readonly) NSString *facebookName;
//... some other code here ...
+(instancetype)manager;
//... some more code here to access/modify the object ...
@end
implementation file
@implementation INTUser
//... some other code here ...
+ (instancetype)manager
{
static INTUser *selfUser = nil;
@synchronized(self) {
if (selfUser == nil) {
selfUser = [[self alloc] init];
}
}
return selfUser;
}
//... some more code ...
@end
usage
Then we use it like this…
INTUser *user = [INTUser manager];
##how it works From the Apple documentation regarding the @synchronized directive:
The @synchronized directive is a convenient way to create mutex locks on the fly in Objective-C code. The @synchronized directive does what any other mutex lock would do—it prevents different threads from acquiring the same lock at the same time. In this case, however, you do not have to create the mutex or lock object directly. Instead, you simply use any Objective-C object as a lock token, as shown in the following example:
- (void)myMethod:(id)anObj
{
@synchronized(anObj)
{
// Everything between the braces is protected by the @synchronized directive.
}
}
We create a mutex using Apple’s @synchronized
directive to prevent anyone else from setting ourself (say what?).
Some sources claim that this method is slow, and that when you pass nil
—which would essentially happen the first run through—the mutex is actually doing nothing.
I haven’t been able to uncover anything specifically mentioning that you can’t use the @synchronized
directive on self
or its behavior when you pass nil
, but I did find some evidence that it might be slow. Which is really just Apple making a generic statement about thread synchronization as a whole:
Synchronization tools are a useful way to make your code thread-safe, but they are not a panacea. Used too much, locks and other types of synchronization primitives can actually decrease your application’s threaded performance compared to its non-threaded performance. Finding the right balance between safety and performance is an art that takes experience. The following sections provide tips to help you choose the appropriate level of synchronization for your application.
Apple’s documentation continues under a section titled Avoid Synchronization Altogether:
Avoid Synchronization Altogether For any new projects you work on, and even for existing projects, designing your code and data structures to avoid the need for synchronization is the best possible solution. Although locks and other synchronization tools are useful, they do impact the performance of any application. And if the overall design causes high contention among specific resources, your threads could be waiting even longer.
The best way to implement concurrency is to reduce the interactions and inter-dependencies between your concurrent tasks. If each task operates on its own private data set, it does not need to protect that data using locks. Even in situations where two tasks do share a common data set, you can look at ways of partitioning that set or providing each task with its own copy. Of course, copying data sets has its costs too, so you have to weigh those costs against the costs of synchronization before making your decision.
So it seems like the general message here is to avoid using synchronization wherever possible.
Method 2. dispatch_once
After finding more recent results from rewording my search terms, I came across the dispatch_once
method, which replaces the @synchronize
directive with dispatch_once
.
header file
implementation
usage
how it works
We replace the @synchronize
directive with a dispatch_once
.
dispatch_once
guarantees us that this code will only be fired once, and other threads should be blocked from this as well, which is important because we only want this code to fire once, even if it’s hit by two separate threads. dispatch_once
is also synchronous.
It is recommended to use dispatch_once
because it is much faster than @synchronized
and it is also more clear that not only do we want our code to be thread safe, but more importantly we only want this code block to be executed one time during our program’s execution. Even if we used the @synchronized
method, the if statement would still be evaluated every time we call our sharedInstance:
method.
The method naming convention also changed (at least from the examples that I found) to a more specific name, sharedInstance:
. This lets us know that it is retrieving an instance of the class and the instance it retrieves will be shared across the application.
There’s also a discussion on stackoverflow that confirms the dispatch_once
method is actually a replacement for the @synchronized
idiom, and may offer some added value from a short read.
If you care to learn more about dispatch_once
I found a blog article that reveals the secrets of dispatch_once
. Which was a little bit more than I cared to read about.
Conclusion
After getting all hung up with the proper way to implement a singleton, I stumbled across another article titled Avoid Singleton Abuse, which talks about why using a singleton in general might be a bad idea. He also talks about using dependency injection as a plausible solution to using too many singletons.
He explains that in most cases even though when we are starting the development of our application we tend to believe, “Oh I will only ever have one instance of this object,” a lot of the times we update our applications in ways that prove us wrong. So it’s important to realize that a singleton is a global scope in sheep’s clothing. Be aware that it will create application states that will have to be maintained if there is ever a change to the behavior of that application.
After sifting through those last two blog posts I came to the conclusion that I could have probably better resolved my original issue with the use of DI by injecting through a constructor, and I will probably be looking to update this code in the near future.
blog comments powered by Disqus