This is the 17th day of my participation in the August Challenge
Singletons may not be implemented exactly the same in different languages, but the idea should be the same: a singleton class has only one instance during the entire program run; Therefore, during the development process, use the singleton class appropriately according to the requirements;
Advantages and disadvantages of singletons
advantages
- A class is instantiated only once, providing controlled access to a unique instance
- Save system resources
- A variable number of instances is allowed
Because of the above features, it is more convenient to transfer values, store state and so on for individual scenarios in the project
disadvantages
- Having only one object for a class can be too much of a liability, to some extent violated
Single responsibility principle
- Because there is no layer of abstraction in the singleton pattern, it is difficult to extend the singleton class
- Abuse of singletons will bring some negative problems, such as: in order to save resources, the database connection pool object is designed as a singleton class, may lead to too many programs sharing the connection pool object and the connection pool overflow; If an instantiated object is not used for a long time, the system considers it garbage and collects it, resulting in a loss of object state
- Once a singleton instance is created, the object pointer is stored in the static section, and the space allocated in the heap is released only after the application terminates
Implementation of singletons
Objective-C
The first way
- Create a singleton class
+ (instancetype)shareInstance {
static AppManager *instance = nil ;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (instance == nil) { instance = [[AppManager alloc] init]; }});return instance;
}
Copy the code
- Prevent errors caused by alloc, init, new
// Prevent external calls to alloc or new
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
return [AppManager shareInstance];
}
Copy the code
- Prevent errors caused by NSCopying
// Prevent external calls to copy
- (id)copyWithZone:(nullable NSZone *)zone {
return [AppManager shareInstance];
}
Copy the code
- Prevents errors caused by mutableCopy
// prevent external calls to mutableCopy - (id)mutableCopyWithZone:(nullable NSZone *)zone {return [AppManager shareInstance]; }Copy the code
The second way
Once created, we directly forbid external init, new, etc.;
Let’s add the following code to the.h file of the singleton:
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (id)copy NS_UNAVAILABLE; // Do not write without following the protocol
- (id)mutableCopy NS_UNAVAILABLE; // Do not write without following the protocol
Copy the code
Swift
class AppManager: NSObject {
static let sharedInstance = AppManager(a)// Privatize the constructor to prevent other objects from using the class's default '()' method for initialization
private override init(a){}}Copy the code
Matters needing attention
- A singleton must be unique, so it is called a singleton. In the life cycle of an application, there is one and only one instance. The existence of singletons gives us a unique global state. Such as
NSNotificaton
.Application
.NSuserDefault
These are all singletons. - In order to keep a singleton unique, singleton
The constructor
It has to be private. This prevents other objects from creating instances of the singleton class. - To ensure that a singleton is unique throughout the life cycle of an application, it must be thread-safe. Simply put, if you write the singleton incorrectly, it is possible for two threads to try to initialize the same singleton at the same time, potentially risking two different singleton. That means we need to use
GCD
thedispatch_once
To ensure that the code that initializes the singleton is executed only once at runtime.
So why doesn’t a singleton created with Swift see dispatch_once?
According to the official documentation:
The delay constructor for global variables (also applicable to static members of structures and enumerations) runs on the first access to the global variable and is started as dispatch_once to ensure that initialization is atomic. This makes it a cool way to use dispatch_once in code: just declare a global variable with an initializer and mark it private.
Basic principles of dispatch_once singleton
When we write implementations of singletons, we often write something like this:
+ (instancetype)shareInstance { static XXXXX *instance = nil ; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (instance == nil) { instance = [[XXXXX alloc] init]; }}); return instance; }Copy the code
So how is dispatch_once implemented? Let’s take a look:
Dispatch_once_f (dispatch_once_f);
val
is&onceToken
, that is,Global static variable
; (Used as a conditional control)block
Is the task to be performed;_dispatch_Block_invoke(block)
Is the encapsulation of the task;
Next, let’s look at the implementation of dispatch_once_f:
dispatch_once_gate_t
Will:val
Encapsulate into a switch for casting tol
;_dispatch_once_gate_tryenter(l)
When we first create, we will execute this;
Enter the _dispatch_once_gate_tryenter function:
os_atomic_cmpxchg
forAtomic operation
, the lock processing, indicating that there is thread control;_dispatch_lock_value_for_self
A lock on the thread space of the current queue, preventing multithreading (thread-safety issue of the singleton class);
Next go to the _dispatch_once_callout function:
- function
_dispatch_client_callout
For the taskctxt
The implementation; _dispatch_once_gate_broadcast
: Broadcast after the completion of the task;
Two macro definitions, one is executed; _dispatch_once_mark_done
Set dGO_once to DLOCK_ONCE_DONE for dog;
Then in the implementation of dispatch_once_f:
If dgo_once is DLOCK_ONCE_DONE, return.
If DLOCK_ONCE_DONE is not set and _dispatch_once_gate_tryenter is locked, _dispatch_once_WAIT (L) is executed indefinitely for unlocking the lock.