The singleton design pattern is often used in development so that only one instance object of a class is created for the entire life of the program and that instance object is not released as long as the program is not killed. Let’s take a look at the concept of singletons, their use, and how they are created to deepen our understanding.


define

Ensure that a class has only one instance and provide a global access point to access it.


role

  • When this pattern is applied, the singleton class must ensure that only one instance exists. Many times the whole system only needs to have one global object, which helps us coordinate the behavior of the whole system. For example, in APP development, we may need to use user information everywhere, so the user information can be stored in a file when logging in, and the configuration data can be read by a singleton object, and then other objects in the server process can obtain the configuration information through this singleton object. This approach simplifies configuration management in complex environments.
  • In some cases, there may be only one instance of a class. For example, if you write a class to play music, only one instance of that class can play sound at any time. For example, a computer can be connected to several printers, but the computer can only have one printing program, here we can use singleton mode to avoid two printing tasks output to the printer at the same time, that is, I only have one printing program instance in the whole printing process.

To create a singleton

There are two ways to create a singleton, described below

1. Create a singleton using GCD

static id _instance;   

+ (instancetype)allocWithZone:(struct _NSZone *)zone  
{   
    static dispatch_once_t onceToken;   
    dispatch_once(&onceToken, ^{   
        _instance = [super allocWithZone:zone];   
    });   
    return _instance;   
}   

+ (instancetype)sharedInstance  
{   
    static dispatch_once_t onceToken;   
    dispatch_once(&onceToken, ^{   
        _instance = [[self alloc] init];   
    });   
    return _instance;   
}   

- (id)copyWithZone:(NSZone *)zone   
{   
    return _instance;   
}  

- (id)mutableCopyWithZone:(NSZone *)zone {   
    return _instance;   
}Copy the code

2. Mutex

static id _instance;
+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    @synchronized(self) {
        if (_instance == nil) {
            _instance = [superallocWithZone:zone]; }}return _instance;
}
+ (instancetype)sharedInstance
{
    @synchronized(self) {
        if(_instance == nil) { _instance = [[self alloc] init]; }}return _instance;
}
- (id)copyWithZone:(NSZone *)zone
{
    return _instance;
}Copy the code

Both methods allow you to create singletons and ensure that the user gets the same instance from shareInstance, alloc, or copy.

The key to the above code is to ensure that the singleton created is the same in a multithreaded situation.

Let’s take a look at GCD and see what happens if you don’t use dispatch_once and synchronization lock to create a singleton

+ (instancetype)sharedInstance  
{   
 if(_instance == nil) { _instance = [[self alloc] init]; }}Copy the code

Suppose there are two threads: Thread 1 and thread 2 are both calling the shareInstance method to create a singleton, so thread 1 runs to if (_instance == nil) and finds _instance = nil, so it initializes a _instance, assuming thread 2 runs to if. Thread 1 hasn’t created instance _instance yet, so _instance = nil is still valid, and thread 2 will create _instace again.

At this point, two instance objects are created, causing problems.

Solution 1. Use dispatch_once

Dispatch_once guarantees that the dispatch_once program is executed only once, so if thread 1 executes shareInstance and creates an instance object, thread 2 will not execute dispatch_once code again. This ensures that only one instance object is created.

Solution 2: Use mutex

Assuming thread 1 is executing the shareInstance method, the code for creating a singleton inside the Synchronize braces looks like this:

if (_instance == nil) {
            _instance = [[self alloc] init];
        }Copy the code

It will be seen as a mission with a lock on it. Let’s say thread 2 also wants to execute the shareInstance method to create a singleton, but sees the mutex added by thread 1 and goes into sleep mode. Thread 1 will wait until it finishes executing and then wake up to execute the code shown above to create the singleton, but _instance! =nil, so no new instance objects are created. This ensures that only one instance object is created.

However, mutexes affect performance, so it is best to create singletons using GCD.


Macro create singleton

If we needed to create multiple singletons in our program, we would need to write the above code once in each class, which would be tedious.

We can use macros to encapsulate singleton creation, so that any class that needs to create a singleton needs only one line of code.

The implementation code

Singleton. H file = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = # define SingletonH (name) + (instancetype) shared# # name; #define SingletonM(name) \ static id _instance; \ \ + (instancetype)allocWithZone:(struct _NSZone *)zone \ { \ static dispatch_once_t onceToken; \ dispatch_once(&onceToken, ^{ \ _instance = [super allocWithZone:zone]; The \}); \ return _instance; \ } \ \ + (instancetype)shared##name \ { \ static dispatch_once_t onceToken; \ dispatch_once(&onceToken, ^{ \ _instance = [[self alloc] init]; The \}); \ return _instance; \ } \ \ - (id)copyWithZone:(NSZone *)zone \ { \ return _instance; \ }\ \ - (id)mutableCopyWithZone:(NSZone *)zone { \ return _instance; The \}Copy the code

How to call

Suppose we want to use it in class ViewController, call the following method:

Viewcontroller. H file = = = = = = = = = = = = = = = = = = = = = = = = = = = # import < UIKit/UIKit. H > # import "Singleton. H" @ interface viewcontroller: UIViewController SingletonH(viewController) @end ViewController. m file =========================== @interface ViewController () @end @implementation ViewController SingletonM(ViewController) - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"%@ %@ %@ %@", [ViewController sharedViewController],[ViewController sharedViewController], [[ViewController alloc] init],[[ViewController alloc] init]); } @endCopy the code

The output

 <ViewController: 0x7f897061bc00> <ViewController: 0x7f897061bc00> <ViewController: 0x7f897061bc00> <ViewController: 0x7f897061bc00>Copy the code

You can see that the memory addresses of the four objects are exactly the same, indicating that they are the same object