📜 ⬆️ ⬇️

Singleton (Translated from the English chapter "Singleton" from the book "Pro Objective-C Design Patterns for iOS" by Carlo Chung)

In mathematics and logic, a singleton is defined as “a set containing exactly one element”. Therefore, no matter how big the bag is, every time we try to get the ball out of it, we will get the same one. In what situations need a singleton in programming? Think about resources that cannot be copied, but can be shared. For example, a single GPS module is installed on the iPhone and only he is able to determine the current coordinates. The CLLocationManager class from the CLLocationManager framework provides a single entry point to all GPS module services. Someone might think: if you can make a copy of CLLocationManager , is it possible to get an additional set of GPS services for your application? It sounds like a fantasy - you created two software GPS for the price of one hardware. But in reality, you still get only one GPS at a time, since the iPhone has only one GPS, which creates real connections with satellites. So, if you think you have created a super application that can manipulate two separate GPS connections at the same 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 that the class object provides. A pattern with this functionality is called Singleton.
In this chapter, we will explore the possibilities of implementing and using the Singleton pattern in Objective-C and the Cocoa Touch framework on iOS.

What is the Singleton pattern?

The Singleton pattern is perhaps the simplest pattern. Its purpose is to make a class object a single instance on the system. First of all, it is necessary to prohibit the creation of more than one instance of a class. To do this, you can use the factory method (Chapter 4), which should be static, since it does not make sense to allow an instance of a class to create another single instance. Figure 7-1 shows the structure of a simple singleton class.

image
Figure 7-1. The static structure of the pattern Singleton.
')
A static uniqueInstance is a single instance of the class Singleton , represented as a class variable that the static method sharedInstance returns to clients. Normally sharedInstance will check if uniqueInstance is uniqueInstance . If not, the method will create it before returning.

Note. Pattern Singleton: Verifies that there is only one instance of the class and provides a single point of access to it. *
* Original definition provided in GoF Design Patterns (Addison-Wesley,
1994).

When can I use the Singleton pattern?

It makes sense to think about using the pattern Singleton, if:

The Singleton pattern provides a familiar way to create a unique instance with a convenient way to access a shared resource. The method of using a reference to a static global object does not prevent the creation of another instance of the same class. The class method approach, although it can provide a global access point, lacks the flexibility of code separation.
A static global variable contains a single reference to an instance of a class. Another class or method that has access to it actually shares the same copy with other classes or methods that use the same variable. It looks like what we need. Everything looks great as long as the same global variable is used throughout the application. Thus, in fact, the Singleton pattern is not needed. But wait! What if someone on your team defined the same global variable in your code as yours? Then there will be two copies of the same global object in the same application - so the global variable does not actually solve the problem.

A class method provides the ability to split without creating its object. A single resource instance is supported in the class method. However, this approach has a lack of flexibility if a class requires inheritance to provide more functionality.

The Singleton class can guarantee a single, consistent and well-known access point for creating and accessing a single class object. The pattern provides such flexibility that any of its subclasses can override the method of creating an instance and have full control over the creation of a class object without changing the client code. Even better, the implementation of the instance creation method can handle the creation of an object dynamically. The actual class type can be determined at runtime to ensure that the correct object is created. This technique will be discussed further.

There is also a flexible version of the Singleton pattern, in which the factory method always returns the same instance, but it is possible in addition to allocate and initialize other instances. This less stringent version of the pattern is discussed in the section “Using the NSFileManager class” later in this chapter.

Implementing Singleton in Objective-C

There is something to think about in order to design the Singleton class correctly. The first question to ask is how to make sure that only one instance of the class can be created? Clients in an application written in other object-oriented languages, such as C ++ and Java, cannot create a class object if its constructor is declared private. And what about Objective-C?

Any Objective-C method is open, and the language itself is dynamically typed, so any class can send a message to another (method call in C ++ and Java) without significant checks at compile time (only compiler warnings if the method is not declared). Also, the Cocoa framework (including the Cocoa Touch) uses memory management by reference counting to maintain the lifetime of an object in memory. All these features make the implementation of Singleton in Objective-C quite complicated.
In the original example of the book “Design Patterns”, the example of Singleton in C ++ looked like Listing 7-1.

Listing 7-1. The original C ++ pattern example Singleton from the book 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 described in the book, the implementation in C ++ is simple and straightforward. In the static method Instance() static variable _instance checked to 0 ( NULL ). If so, a new object of class Singleton and then returned. Some of you might think that the Objective-C version is not much different from its counterpart and should look like in listings 7-2 and 7-3.

Listing 7–2. Singleton class declaration in Singleton.h

 @interface Singleton : NSObject { } + (Singleton *) sharedInstance; @end 

Listing 7–3. Implementing the sharedInstance Singleton.m method

 @implementation Singleton static Singleton *sharedSingleton_ = nil; + (Singleton *) sharedInstance { if (sharedSingleton_ == nil) { sharedSingleton_ = [[Singleton alloc] init]; } return sharedSingleton_; } @end 

If so, then this is a very simple chapter, and you have already studied one pattern implemented in Objective-C. In fact, there are several difficulties that need to be overcome to make the implementation reliable enough to be used in a real application. If you need a “strict” version of the Singleton pattern, then there are two main problems that need to be solved so that it can be used in real code:

Listing 7-4 shows an implementation that is close to the one we are aiming for. The code is quite long, so we break it into several parts for ease of discussion.

Listing 7-4. More suitable 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 as in the first example, it is first checked that a single instance of the class is created, otherwise a new one is created and returned. But this time, it calls [[super allocWithZone:NULL] init] to create a new instance instead of using other methods, such as alloc . Why super and not self ? This is because the basic methods for allocating memory for an object in our class are redefined, so you need to “borrow” this functionality from the base class, in this case from NSObject , to help make the low-level routine work of allocating memory for us.

 + (id) allocWithZone:(NSZone *)zone { return [[self sharedInstance] retain]; } - (id) copyWithZone:(NSZone*)zone { return self; } 


There are several methods that relate to memory management in the Singleton class that you need to think about. In the allocWithZone:(NSZone *)zone method, the class instance that is returned from the sharedInstance method sharedInstance . In the Cocoa Touch framework, when calling the class allocWithZone:(NSZone *)zone class method, the memory for the 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 alloc used in many situations; in fact, alloc allocWithZone: with the zone set to NULL to allocate memory for the instance in the default zone. The details of creating an object and managing memory are outside of this book. You can consult the documentation for further clarification.

Similarly, you need to override the copyWithZone:(NSZone*)zone method to make sure that it does not return a copy of the instance, but returns the same one, returning self .

 - (id) retain { return self; } - (NSUInteger) retainCount { return NSUIntegerMax; // ,       } - (void) release { //    } - (id) autorelease { return self; } @end 


Other methods, such as retain , release and autorelease , are redefined to make sure that they do nothing (in the memory management model with reference counting), except to return self . The retainCount method returns NSUIntegerMax (4,294,967,295) to prevent an instance from being deleted from memory during the life of the application.

Why call retain singleton?
You may have noticed that we call retain a Singleton object that is returned from the sharedInstance method in allocWithZone: but retain overridden and is actually ignored in our implementation. Given this, we will have the opportunity to make the Singleton class less “strict” (that is, it will be possible to allocate memory for additional instances and initialize them, but the factory sharedInstance method always returns the same instance or Singleton object becoming destructible). Subclasses can override the retain , release and autorelease again to provide a suitable implementation of memory management.
The flexible version of the Singleton pattern is discussed in the section “Using the NSFileManager Class” later in this chapter.

We have already discussed in some detail how the Singleton pattern should look like in Objective-C. However, there is one more thing to think about before you can use it. What if we want to inherit from the original class Singleton ? Consider how to do this.

Inheritance from Singleton

The alloc call alloc redirected to super , so the NSObject class NSObject take care of allocating memory for the object. If we inherit from the class Singleton without modifications, then the returned instance will always be of type Singleton . Since the Singleton class overrides all instance-related methods, it is rather difficult to inherit from it. But we were lucky; You can use some Foundation functions to instantiate any object based on its class type. One of them is id NSAllocateObject (Class aClass, NSUInteger extraBytes, NSZone *zone) . Therefore, if we want to instantiate an object of the class called “Singleton”, we can do the following:

 Singleton *singleton = [NSAllocateObject ([Singleton class], 0, NULL) init]; 


The first parameter is the type of the class Singleton . The second parameter is intended for any number of additional bytes for indexed instance variables, which is always 0. The third parameter is the designation of the zone of allocated memory; the default zone is almost always used (the parameter is NULL ). Therefore, you can instantiate any objects using this function, knowing the class type. What should I do to inherit from the class Singleton ? Let's remember that the original version of the sharedInstance method looks like this:

 + (Singleton*) sharedInstance { if (sharedSingleton_ == nil) { sharedSingleton_ = [[super allocWithZone:NULL] init]; } return sharedSingleton_; } 


If you use the trick with NSAllocateObject to create an instance, it will become like this:

 + (Singleton *) sharedInstance { if (sharedSingleton_ == nil) { sharedSingleton_ = [NSAllocateObject([self class], 0, NULL) init]; } return sharedSingleton_; } 


Now it doesn't matter if we instantiate the class Singleton or one of its subclasses, this version will do everything correctly.

Thread safety

The class Singleton in the example is only good for general use. If you need to use a singleton object in a multi-threaded environment, then you need to make it thread-safe. To do this, you can insert @synchronized() blocks or use NSLock instances around checking for nil for the sharedSingleton_ static variable variable. If there are any other properties that need to be protected too, then you can make them atomic .

Using Singltons in the Cocoa Touch framework

In the process of familiarization with the Cocoa Touch Framework documentation, you will meet many different classes of singletons. We will talk about some of them in this section - UIApplication , UIAccelerometer and NSFileManager .

UIApplication class usage

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.

Each application has a single instance of UIApplication . It is created as a singleton object by the UIApplicationMain function when the application is started and is available at runtime via the sharedApplication class sharedApplication .

The UIApplication object performs many service tasks for the program, including initial routing of incoming user messages, as well as dispatching action messages for UIControl objects to the corresponding target objects. It maintains a list of all open UIWindow objects. The application object is associated with the UIApplicationDelegate application delegate object, which is informed of any significant events during the execution of the program, such as startup, low memory warnings, application termination, and background processes. The event handlers allow the delegate to customize the behavior of the application.

Using the UIAccelerometer class

Another common singleton in the Cocoa Touch framework is the UIAccelerometer . The UIAccelerometer class allows an application to subscribe to receive acceleration-related data from the onboard accelerometer in an iOS device. An application can use data on changes in linear acceleration along the main axes in three-dimensional space to determine both the current orientation of the device and instantaneous changes in orientation.
The UIAccelerometer class is a singleton, so you cannot create its objects explicitly. To access its single instance, you need to call the class method sharedAccelerometer . In addition, you can set its updateInterval property and delegate property to your own delegate object to receive any reported acceleration data from a singleton instance.

Using the NSFileManager class

The NSFileManager class was once a “strict” implementation of the Singleton pattern in front of Mac OS X 10.5 and in iOS 2.0. Calling his init method does nothing, and its only instance can be created and accessed only through the defaultManager class defaultManager . However, since the singleton implementation is not thread-safe, new NSFileManager instances must be created to ensure this security. This approach is viewed as a more flexible implementation of the Singleton, in which the factory method always returns the same instance, but additional instances can also be selected and initialized.

If you need to implement a "strict" singleton, you need an implementation similar to the example described in the previous sections. Otherwise, do not override allocWithZone: and other related methods.

findings

The Singleton pattern is one of the most widely used patterns in almost any type of application, and not just for iOS.
Singleton can be useful when a centralized class is required to coordinate application services.
This chapter marks the end of this section on creating objects. In the next part, we will see some design patterns that focus on the adaptation / consolidation of objects with different interfaces.

Source: https://habr.com/ru/post/198470/


All Articles