In software engineering, the singleton pattern is a design pattern used to implement the mathematical concept of a singleton, by restricting the instantiation of a class to one object.
In Cocoa, singleton objects act as global variables which can be allocated only once and can not be deleted. When it comes to global variables, as developers, we are often told that it is bad design and should be avoid. However, they are essential (see this article by Matt Gallagher for more detail) and helpful especially when exactly one object is needed to coordinate actions across the system.
An Example of CLLocationManager
Using CLLocationManager as a singleton object will be a perfect example if you are designing a location-based application and want to access the user location across the whole system. First we need to create a class called LocationController and set it to be the singleton.
In LocationController.h
//
// LocationController.h
//
// Created by Jinru on 12/19/09.
// Copyright 2009 Arizona State University. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
// protocol for sending location updates to another view controller
@protocol LocationControllerDelegate
@required
- (void)locationUpdate:(CLLocation*)location;
@end
@interface LocationController : NSObject {
CLLocationManager* locationManager;
CLLocation* location;
__weak id delegate;
}
@property (nonatomic, strong) CLLocationManager* locationManager;
@property (nonatomic, strong) CLLocation* location;
@property (nonatomic, weak) id delegate;
+ (LocationController*)sharedInstance; // Singleton method
@end
Then in LocationController.m
//
// LocationController.m
//
// Created by Jinru on 12/19/09.
// Copyright 2009 Arizona State University. All rights reserved.
//
#import "LocationController.h"
static LocationController* sharedCLDelegate = nil;
@implementation LocationController
@synthesize locationManager, location, delegate;
- (id)init
{
self = [super init];
if (self != nil) {
self.locationManager = [[CLLocationManager alloc] init];
self.locationManager.delegate = self;
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
}
return self;
}
#pragma mark -
#pragma mark CLLocationManagerDelegate Methods
- (void)locationManager:(CLLocationManager*)manager
didUpdateToLocation:(CLLocation*)newLocation
fromLocation:(CLLocation*)oldLocation
{
/* ... */
}
- (void)locationManager:(CLLocationManager*)manager
didFailWithError:(NSError*)error
{
/* ... */
}
#pragma mark -
#pragma mark Singleton Object Methods
// THE FOLLOWING CODE IS NO LONGER SUPPORTED IN ARC
+ (LocationController*)sharedInstance {
@synchronized(self) {
if (sharedCLDelegate == nil) {
[[self alloc] init];
}
}
return sharedCLDelegate;
}
+ (id)allocWithZone:(NSZone *)zone {
@synchronized(self) {
if (sharedCLDelegate == nil) {
sharedCLDelegate = [super allocWithZone:zone];
return sharedCLDelegate; // assignment and return on first allocation
}
}
return nil; // on subsequent allocation attempts return nil
}
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX; // denotes an object that cannot be released
}
- (void)release {
//do nothing
}
- (id)autorelease {
return self;
}
@end
Now your LocationController class is ready to be used as a singleton in your code. To access its instance just use:
[LocationController sharedInstance];
And you are done.
UPDATE
With the new ARC support in iOS 4 and MacOS 10.6, the implementation of a singleton class needs to be updated. Simply because in ARC, you cannot call or override methods such as retain, release, autorelease and retaincount. Here is how you should implement the LocationController as a singleton class now.
#pragma mark - Singleton implementation in ARC
+ (LocationController *)sharedLocationController
{
static LocationController *sharedLocationControllerInstance = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
sharedLocationControllerInstance = [[self alloc] init];
});
return sharedLocationControllerInstance;
}
To access your singleton class is the same:
LocationController* locationController = [LocationController sharedLocationController];
Hello,
This is exactly what I was thinking I needed to do for a project I’m working on. However, I’m new to iOS development and am not quite clear on how to use the protocol in order to send location updates to another view controller. I know all interested view controllers will need to adopt the LocationControllerDelegate protocol, but how do you actually announce that a new location has changed from the LocationController?
Thanks!
Kevin
Hi Kevin,
This is how you will do it. In LocationController.m, one of the delegation method is called when a new location is updated.
- (void)locationManager:(CLLocationManager*)manager didUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation*)oldLocation
{
/*…some filer method to check if the new location is good …*/
if (good)
{
[self.delegate locationUpdate:newLocation];
}
}
Now in your other view controller that adopts the protocol, you first set it up to be the delegate of LocationController, something like [LocationController sharedInstance].delegate = self; in viewdidload will do.
Then you implement the protocol method locationUpdate:(CLLocation*)location to utilize the updated location the LocationController has received. eg: NSString* latitudeString = [[NSString alloc] initWithFormat:@”%f”, location.coordinate.latitude];
Hope it helps.
Perfect Jinru! Thanks so much. Excellent tutorial.
Hi kevin, this singleton is very useful. Do you mean adding this to the viewcontroller?
- (void)locationUpdate:(CLLocation *)location{
[[LocationController sharedInstance] setLocation:location];
}
Also i am working with ARC and the line:
[LocationController sharedInstance].delegate = self;
gives me a warning (passing const___strong to parameter of incompatible type id)
locationUpdate:(CLLocation*)location method will be called automatically when a new location is available. You implement this method in your viewcontroller that wants to do something with the new location (not to set the location for the singleton as in your code, singleton already has the new location).
try [LocationController sharedInstance].delegate = (id)self to get rid of the warning.
Hello.
I tried this
(LocationController*)sharedInstance {
@synchronized(self) {
if (sharedCLDelegate == nil) {
[[self alloc] init];
}
}
return sharedCLDelegate;
}
but during code analyze i receive potential memory leak…
Thanks for letting me know Peter. Try [[[self alloc] init] autorelease] and see if it gets rid of the warning. Usually, a singleton stays around forever(as long as your application is running), so it is supposed to remain in the memory.
Hi this looks like what I was looking for. However I am new to iphone development and think I am missing something probably very basic.
I have created the LocationController h and m file.
What do I then need to do to get the lat long in a different view to both the header file and m file. You say use [LocationController sharedInstance].delegate = self; in viewdidload but what else do I need to do. How would I then set the lat long as a variable to use.
Sorry if this is very basic but I have been looking into this for days.
Hi weirdo1978,
Since the view controller adopts the LocationControllerDelegate protocol. In its .m file, you need to implement the delegate method locationUpdate:(CLLocation*)location to utilize the updated location the LocationController has received. eg: NSString* latitudeString = [[NSString alloc] initWithFormat:@”%f”, location.coordinate.latitude];
Let me know if you still have questions.
Many thanks for the reply
I have created a basic view based app and then created the PhoneLocation.h and m file as above.
If i then add
- (void)viewDidLoad
{
[super viewDidLoad];
NSString* latitudeString = [[NSString alloc] initWithFormat:@”%f”, location.coordinate.latitude];
}
to my viewcontroller.m file it gives an error of unexpected @
Am I missing something.
Sorry about this
Sorry. Should be @”%f” instead of @”%f”. @”" is an indication of NSString. However, if you want to receive the location updates(lat/long), you still need to implement the locationUpdate:(CLLocation*)location method in your view controller because of the Delegation pattern. Delegation is a key design pattern in Cocoa. I have a post that talks about that.
Also, read “learn objective-c on the mac” if you want to learn the basics of Objective-C.
Hope this helps.
This is pretty awesome stuff jinru – couple questions.
the “[[self alloc] init];” line (in ARC) gives the warning: Expression result unused.
Also, I’ve implemented this in two classes and it seems the first class to call:
[LocationController sharedInstance].delegate = self;
is the only one to receive updates. Any ideas?
Hi capikaw,
The singleton code above is written before ARC. In ARC, this will cause warnings/errors simply because you cannot override methods such as retain, release, releasecount, autorelease anymore. I need to update this post, sorry. But basically here is how you will implement the singleton class:
+ (SomeClass *)sharedSomeClass
{
static SomeClass *sharedSomeClassInstance = nil;
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
sharedSomeClassInstance = [[self alloc] init];
});
return sharedSomeClassInstance;
}
and that is it. This piece of code is supported in ARC and is thread safe:-)
Would you please post an ARC updated tutorial or the code in full.
I personally don’t use ARC much but I found this article very useful http://www.learn-cocos2d.com/2011/11/everything-know-about-arc/
Would you mind posting the full code for the ARC updated version? I think I replaced the code appropriately, but I’m getting a few warnings and errors (mostly around the delegate ivar).
Great info! Thanx for sharing. It saved my project
I had a serious problem figuring out how to handle background location tracking and location tracking while view not-in-sight. This helped me to figure it out.
Hello Jinru, first of all thanks for this post! What I am curios about is the case how to solve the initial test if `kCLAuthorizationStatusAuthorized` is authorized? When someone is initially starting an application he/she has to allow current location service. This case seems not to be checked in your example or other way round. The init Method has to check kCLAuthorizationStatusAuthorized first before creating a CLLocationManager instance, right? How do I have to solve this initial case? Any idea?
Best, dominik
Hi Dominik,
You should take a look at the CLLocationManagerDelegate Protocol (http://developer.apple.com/library/ios/#documentation/CoreLocation/Reference/CLLocationManagerDelegate_Protocol/CLLocationManagerDelegate/CLLocationManagerDelegate.html). You could use locationManager:didChangeAuthorizationStatus: to check the status. Meanwhile, you can always check the status by calling [CLLocationManager authorizationStatus]. Since it’s a class method on CLLocationManager you don’t have to instantiate CLLocationManager to use it.
Hi Jinru,
the question here is do I have to check the authorizationStatus outside (before) the init call or could this be handled inside this class by implementing locationManager:didChangeAuthorizationStatus? The charm would be an automatic reinitialization of the CLLocationManager when the locationManager:didChangeAuthorizationStatus is called. Then the hole CLLocationManager logic is isolated in this place and no check need to be done outside this class. Additionally the init method will be done correctly by checking the authorizationStatus.
I’ve successfully implemented this approach in my mobile app and it works great. I have also eliminated all of the ARC warnings except one.
self.locationManager.delegate = self;
This line gives the warning “Assigning to ‘id’ from incompatible type ‘LocationController *__strong’ ”
I’m not sure how to change this to make it work with ARC for iOS6. Any suggestions?