In mathematics and logic, a singleton is defined as "a set that contains exactly one element." So no matter how big a pouch is, every time you are trying to get a marble out of it you will get only the same one. Under what situations do we need a singleton in software? Think about resources that can be shared only in a system and no copies of them can be made available to others. For example, the GPS device is the sole hardware on the iPhone that provides coordinates of the device in real time. The CLLocationManager
class of the CoreLocation
framework provides a single access point to any services that the GPS device offers. Some people may think, if I can make a copy of CLLocationManager
, then can I get an extra set of GPS services to my application? That sounds like a fantasy—you create two copies of software GPS for the price of one hardware GPS. But in reality, you still get only one GPS at a single time because there is only one GPS in the device that makes actual connections with satellites in the sky. So if you think you wrote a killer app that can manipulate two separate GPS connections at a time and want to brag about it to your friends, think twice!
A singleton class in an object-oriented application always returns the same instance of itself. It provides a global access point for the resources provided by the object of the class. A design pattern that is related to these kinds of designs is called the Singleton pattern.
In this chapter, we will be exploring some possibilities for implementing and using the Singleton pattern in Objective-C and the Cocoa Touch framework for the iOS.
The Singleton pattern is almost the simplest form of design pattern ever. The intent of the pattern is to make an object of a class be the sole instance in a system. To achieve that, we can begin with the point where clients instantiate it. So we need to "block" every access to create the object with a mechanism that allows only a single instance of the object's class to go out. We can use a factory method (Chapter 4) to bottleneck the instantiation process. That method should be a static method (class method), as it wouldn't make sense to allow an instance of the class to create another sole instance. Figure 7-1 shows a class structure of a simple singleton.
The static uniqueInstance
is the only instance of Singleton
as a class variable with which static sharedInstance
will return it to clients. Usually, sharedInstance
would check and see if uniqueInstance
is instantiated. If not, it will create one before returning the uniqueInstance
.
THE SINGLETON PATTERN: Ensures a class has only one instance, and provide a global point of access to it.[5]
You'd think about using the Singleton pattern when
There must be exactly one instance of a class with which it must be accessible from a well-known access point, e.g., a factory method (Chapter 4).
The sole instance can be extended only by subclassing, and it won't break client code with the extended object.
The Singleton pattern provides a well-known access point to client classes that want to create a unique instance of and access to a shared resource. Although a static global object reference or a class method can provide a global access point, the global object cannot prevent the class getting instantiated more than once, and the class method lacks the flexibility of decoupling.
A static global variable holds a single reference to an instance of a class. Another class or a method that can access that global variable is, in fact, sharing the same copy with other classes or methods that use the same variable. That sounds like what we are after in this chapter. Everything seems fine if we use only the same global variable throughout the whole application. So, in fact, we don't need the Singleton pattern. But hey, wait a minute; what if there is somebody in your team or a consultant who has defined the same type of static global variable as yours? Then there will be two copies of the same global object type living in the same application—so a global variable doesn't really solve the problem.
A class method provides shared services without creating an object of it. A single instance of the resource is maintained within the class method. However, this approach lacks the flexibility if the class needs to be subclassed to provide better services.
A singleton class can guarantee a single, consistent, and well-known access point to create and access a single object of the class. The pattern provides the flexibility such that any of its subclasses can override the instance method and have total control over object creation of itself without changing code in the client. Or even better, the instance implementation in the parent class can handle dynamic object creation. The actual type of a class can be determined to make sure the correct object is created at runtime. This technique will be discussed later in the chapter.
There is a flexible version of the Singleton pattern in which a factory method always returns the same instance, but you can also allocate and initialize additional instances. This less "strict" version of the pattern is discussed in "Using the NSFileManager
Class" later in this chapter.
There are some things that we need to think about in order to design a singleton class properly. The first question we need to ask is, how can we make sure there is only one instance of a class that can be created? Clients in an application written in other object-oriented programming languages, like C++ and Java, cannot create an object of a class if its constructor is declared private
. Then what about Objective-C?
Every Objective-C method is public and the language itself is dynamically typed, so every class can send each other's messages (method calling in C++ and Java) without much compile time checking (only compiler warnings if a message in question is not declared). Also the Cocoa (including Cocoa Touch) framework utilizes reference counting memory management to maintain objects' life span in memory. All of these features make implementing a singleton in Objective-C quite challenging.
In the original example of the Design Patterns book, the C++ example of the Singleton pattern is as shown in Listing 7-1.
Example 7-1. The Original C++ Singleton Class Appearing in Design Patterns
class Singleton { public: static Singleton *Instance(); protected: Singleton(); private: static Singleton *_instance; };
Singleton *Singleton::_instance = 0; Singleton *Singleton::Instance () { if (_instance == 0) { instance = new Singleton; } return _instance; }
As it is described in the book, the implementation in C++ is simple and straightforward. In the static Instance()
method, the static _instance
instance variable is checked to see if it is 0 (NULL)
. If so, a new Singleton
object will be created and then an instance of it will be returned. Some of you may think an Objective-C version shouldn't be much different from its brother's version and should look like Listings 7-2 and 7-3.
Example 7-2. A Class Declaration of Singleton in Singleton.h
@interface Singleton : NSObject { } + (Singleton *) sharedInstance; @end
Example 7-3. The Implementation of the sharedInstance
Method in Singleton.m
@implementation Singleton static Singleton *sharedSingleton_ = nil; + (Singleton *) sharedInstance { if (sharedSingleton_ == nil) { sharedSingleton_ = [[Singleton alloc] init]; } return sharedSingleton_; } @end
If that's the case, then this is a very easy chapter to write and read, and you've already learned one pattern implemented in Objective-C. In reality, there are some hurdles that we need to get over to make the implementation robust enough to live in a real application. If you need to implement a "strict" version of the Singleton pattern, then there are two major hurdles that you need to address so it can be used in the real world:
Listing 7-4 shows an implementation that should be close to the one that we are looking at. The code is quite long, so we will break it up into a few parts to discuss.
Example 7-4. A More Appropriate Implementation of Singleton in Objective-C
#import "Singleton.h"
@implementation Singleton
static Singleton * sharedSingleton = nil;
+ (Singleton*) sharedInstance
{
if (sharedSingleton_ == nil)
{
sharedSingleton_ = [[super allocWithZone:NULL] init];
}
return sharedSingleton_;
}
Inside the sharedInstance
method, just like in the first example, it first checks if the sole instance of the class is created, otherwise it creates a new one and returns it. But this time, it calls [[super allocWithZone:NULL] init]
to create a new instance instead of using other methods such as alloc
. Why super
but not self
? It's because we have overridden the basic object allocation methods in self
, so we need to "borrow" the functionality from its parent, in this case NSObject
, to help do the low-level memory allocation chores for us.
+ (id) allocWithZone:(NSZone *)zone
{
return [[self sharedInstance] retain];
}
- (id) copyWithZone:(NSZone*)zone
{
return self;
}
There are a few methods that are related to the memory management, which we need to take care of in the Singleton
class. In allocWithZone:(NSZone *)zone
, the method returns only the class instance that is returned from the sharedInstance
method. In the Cocoa Touch framework, by calling a class's allocWithZone:(NSZone *)zone
method, memory of an instance will be allocated, its reference count will be set to 1, and the instance will be returned. We have seen that the alloc
method is used in many situations; in fact, alloc
calls allocWithZone:
with the zone
set to NULL
to allocate memory for the new instance in the default zone. The details of object creation and memory management are outside the scope of this book. You can consult the documentation for any further details.
Likewise, we also need to override the copyWithZone:(NSZone*)zone
method to make sure it won't return a copy of the instance but the same one by returning self
.
- (id) retain
{
return self;
}
- (NSUInteger) retainCount
{
return NSUIntegerMax; // denotes an object that cannot be released
}
- (void) release
{
// do nothing
}
- (id) autorelease
{
return self;
}
@end
Other methods, like retain
, release
, and autorelease
, are overridden to make sure they won't do anything (in a reference counting memory model) except just to return self
. retainCount
returns NSUIntegerMax
(4,294,967,295) to keep the instance alive as long as the application lives.
We have pretty much covered what a singleton in Objective-C should look like. However, there is still something else that we need to think about carefully before we can use it. What if we want to subclass the original Singleton
? We will answer that question now.
The alloc
call is forwarded to super
, which means NSObject
will take care of the object allocation. If we subclass Singleton
without any modification, the returned instance is always Singleton
. Because Singleton
overrides all the instantiation-related methods, it is quite tricky to subclass it. But we are quite lucky; we can use some Foundation functions to instantiate whatever object based on its class type. One of them is id NSAllocateObject (Class aClass, NSUInteger extraBytes, NSZone *zone)
. So if we want to instantiate an object of a class called "Singleton," we can do the following:
Singleton *singleton = [NSAllocateObject ([Singleton class], 0, NULL) init];
The first parameter is the type of the Singleton
class. The second parameter is for any extra bytes for indexed instance variables, which is always 0. The third parameter is to specify an allocated zone in memory; it is NULL
for a default zone most of the time. So you can instantiate whatever objects with this function by providing their class types. What does it have to do with subclassing Singleton
? Let's recall that the original sharedInstance
method looks like this:
+ (Singleton*) sharedInstance { if (sharedSingleton_ == nil) { sharedSingleton_ = [[super allocWithZone:NULL] init]; } return sharedSingleton_; }
If we use the trick of NSAllocateObject
to create an instance, then it will become like this:
+ (Singleton *) sharedInstance
{
if (sharedSingleton_ == nil)
{
sharedSingleton_ = [NSAllocateObject([self class], 0, NULL) init];
}
return sharedSingleton_;
}
So now no matter whether we are instantiating Singleton
or any of its subclasses, that version should take care of that nicely.
If a singleton object is intended to be accessed by multiple threads, then it is crucial to make it thread-safe. The Singleton
class in the example is good only for general use. To make it thread-safe, we can put some @synchronized()
blocks or NSLock
instances around the nil
check for the sharedSingleton_
static instance. If there are some other properties that also need to be protected, we can make them atomic
as well.
When you wade through the Cocoa Touch Developer's documentation, you will find numerous singleton classes in the framework here and there. We will talk about a few of them in this section, UIApplication
, UIAccelerometer
, and NSFileManager
.
One of the most commonly used singleton classes in the framework is the UIApplication
class. It provides a centralized point of control and coordination for iOS applications.
Every app has exactly one instance of UIApplication
. It's created as a singleton object by the UIApplicationMain
function when an app is launched. Thereafter, the same instance of the UIApplication
can be accessed through its sharedApplication
class method.
A UIApplication
object does many housekeeping tasks for an application, including initial routing of incoming user events as well as dispatching action messages for UIControl
objects to appropriate target objects. It also maintains a list of all the UIWindow
objects being opened in the app. The application object is always assigned with a UIApplicationDelegate
object, to which the application informs any significant runtime events, such as application did launch, low-memory warnings, application termination, and background process executions in an iOS application. They give an opportunity to the delegate to respond appropriately.
Another common singleton in the Cocoa Touch framework is the UIAccelerometer
. The UIAccelerometer
class allows an application to sign up to receive acceleration-related data from the built-in accelerometer in an iOS device. An application can use reported linear acceleration changes along the primary axes in three-dimensional space to detect both the current orientation of the device and any instantaneous changes to that orientation.
The UIAccelerometer
is a singleton, so you cannot create its objects directly. Instead, its sharedAccelerometer
singleton class method should be called to access its sole instance. Then you can set its updateInterval
as well its delegate
properties with your own delegate object to receive any reported acceleration data from the singleton instance.
The NSFileManager
used to be a "strict" implementation of the Singleton pattern prior Mac OS X v 10.5 and in iOS 2.0. An invocation to its init
method is a no-op (does nothing), and its only instance can be created and accessed only through the defaultManager
class method. However, its singleton implementation is not thread-safe. Now creating new instances of NSFileManager
is recommended for thread-safety. This approach is considered a more flexible singleton implementation in which a factory method always returns the same instance, but you can allocate and initialize additional instances as well.
If you need to implement a "strict" singleton, you need to have an implementation similar to the example described in the previous sections. Otherwise, do not override allocWithZone:
and the other methods following it.
The Singleton pattern is one of the most commonly used patterns in almost any type of application, not just for iOS application development.
As long as an application requires a centralized class that coordinates the services of its own, the class should generate a singleton instance rather than multiple instances.
This chapter marks the end of this part about object creation. In the next part, we will see some design patterns that focus on adapting/consolidating objects with different interfaces.
[5] The original definition appeared in Design Patterns, by the "Gang of Four" (Addison-Wesley, 1994).
18.218.97.75