This is the 11th day of my participation in the August More Text Challenge. For details, see:August is more challenging

Write in the front: the exploration of the underlying principles of iOS is my usual development and learning in the continuous accumulation of a step forward road. I hope it will be helpful for readers to record my journey of discovery.Copy the code

The directory is as follows:

  1. Exploring the underlying principles of iOS alloc
  2. Exploration of the underlying principles of iOS structure in vivo alignment
  3. The nature of the object explored by iOS underlying principles & the underlying implementation of ISA
  4. The underlying principles of the ISA-class (part 1)
  5. The underlying principles of the ISA class (middle)
  6. The underlying principles of isA-Class (part 2)
  7. Exploring the nature of Runtime Runtime & Methods in iOS Underlying principles
  8. Objc_msgSend explores the underlying principles of iOS
  9. Runtime Runtime slow lookup process
  10. Dynamic method resolution for exploring the underlying principles of iOS
  11. IOS underlying principles to explore the message forwarding process
  12. IOS Application loading principle
  13. Application loading principle (2)
  14. IOS underlying principle exploration class load
  15. IOS Underlying principles to explore the classification of loading
  16. Associated object for iOS underlying principle exploration
  17. Exploration of iOS underlying principles of sorcerer KVC
  18. Exploring the underlying principles of iOS — KVO Principles | more challenging in August
  19. Exploration of the underlying principles of iOS rewrite KVO | August more text challenges
  20. Exploring the underlying principles of iOS multithreading | more challenging in August
  21. Exploring the underlying principles of iOS: GCD functions and queues
  22. GCD Principles (part 1)
  23. IOS Low-level – What do you know about deadlocks?

Summary column for the above

  • Summary of the phase of iOS underlying principle exploration

Tidy up the details

  • Summary of iOS development details

preface

Singletons, a design that we use a lot in development, have you ever thought,

  • Why is it executed only once in the life cycle of the app?
  • What does the underlying system do to achieve this?
  • Also, can singletons be destroyed?

With these questions in mind, we begin today’s presentation.

The singleton

static dispatch_once_t onceToken;   
dispatch_once(&onceToken, ^{  
    class = [[self alloc] init];
});
Copy the code

The two most important parameters in simple interest are onceToken and block.

There are two core points:

  1. Why is simple interest called once?
  2. Why is this block of simple interest called?

First, let’s write a singleton to debug breakpoints:

+ (instancetype)shareSM {
    
    static SMObject *class = nil;
    static dispatch_once_t predicate;
    
    NSLog(@"1:%ld", predicate);
    
    dispatch_once(&predicate, ^{
        NSLog(@"2:%ld", predicate);
        class = [[SMObject alloc] init];
    });
    
    NSLog(@"3:%ld", predicate);
    return class;
}
Copy the code

When executing a block of dispatch_once, let’s look at the stack information:

The first time we call the sharSM method, the execution comes:

_dispatch_once_callout -> _dispatch_client_callout

We have had previous experience with GCD source code exploration, and it is clear that this block is invoked in the _dispatch_client_callout function. The stack information here is further validation.

You can see the value of the predicate as the program executes:

  • It starts at 0;
  • Block changed to 256 during execution.
  • And then it gets -1 before return.

Next, let’s look at the underlying source implementation of the diapatch_once function:

dispatch_once

void
dispatch_once(dispatch_once_t *val, dispatch_block_t block)
{
	dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
Copy the code

dispatch_once_f

void
dispatch_once_f(dispatch_once_t *val, void *ctxt, dispatch_function_t func)
{
	dispatch_once_gate_t l = (dispatch_once_gate_t)val;

#if! DISPATCH_ONCE_INLINE_FASTPATH || DISPATCH_ONCE_USE_QUIESCENT_COUNTER uintptr_t v = os_atomic_load(&l->dgo_once, acquire);// The status is DLOCK_ONCE_DONE
	if (likely(v == DLOCK_ONCE_DONE)) {
		return;
	}
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
	if (likely(DISPATCH_ONCE_IS_GEN(v))) {
		return _dispatch_once_mark_done_if_quiesced(l, v);
	}
#endif
#endif
        // The first time a lock is acquired, the atomic operation is multithreaded
	if (_dispatch_once_gate_tryenter(l)) {
        
                // Execute the call
		return _dispatch_once_callout(l, ctxt, func);
	}
        
        // If the lock is locked, it will wait for the lock to be unlocked
	return _dispatch_once_wait(l);
}
Copy the code

Following the internal process of the dispatch_once_f function, we explain that it is called once because: The internal implementation of val (static dispatch_once_t predicate) is encapsulated as dispatch_once_gate_t, which is used to obtain an association between the underlying atomicity. Associate a variable of uintPtr_t type V to query. The current onceToken is a global static variable. Depending on each simple interest, each static variable is different. In order to ensure uniqueness, the underlying version uses the kVC-like format of OS_atomic_LOAD. If the retrieved value is DLOCK_ONCE_DONE: it has been processed once, then the tune is returned. When the first code execution comes in: the thread locks itself for security, ensuring that the current task is executed uniquely, preventing the same onceToken from being executed multiple times. After the lock, the call to the block executes. At the end of the call, the lock is unlocked and the value of v is set to DLOCK_ONCE_DONE (the next time the block is not called). So the uniqueness of simple interest is guaranteed.

_dispatch_once_callout

static void
_dispatch_once_callout(dispatch_once_gate_t l, void *ctxt,
		dispatch_function_t func)
{
	_dispatch_client_callout(ctxt, func);
        // Broadcast after processing_dispatch_once_gate_broadcast(l); }...static inline void
_dispatch_once_gate_broadcast(dispatch_once_gate_t l)
{
	dispatch_lock value_self = _dispatch_lock_value_for_self();
	uintptr_t v;
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
	v = _dispatch_once_mark_quiescing(l);
#else
	v = _dispatch_once_mark_done(l);
#endif
	if (likely((dispatch_lock)v == value_self)) return; _dispatch_gate_broadcast_slow(&l->dgo_gate, (dispatch_lock)v); }...static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
    // Match first and then change to DLOCK_ONCE_DONE
    DLOCK_ONCE_DONE = DLOCK_ONCE_DONE = DLOCK_ONCE_DONE
    return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}

Copy the code

_dispatch_once_gate_broadcast

static inline void
_dispatch_once_gate_broadcast(dispatch_once_gate_t l)
{
	dispatch_lock value_self = _dispatch_lock_value_for_self();
	uintptr_t v;
#if DISPATCH_ONCE_USE_QUIESCENT_COUNTER
	v = _dispatch_once_mark_quiescing(l);
#else
	v = _dispatch_once_mark_done(l);
#endif
	if (likely((dispatch_lock)v == value_self)) return; _dispatch_gate_broadcast_slow(&l->dgo_gate, (dispatch_lock)v); }...static inline uintptr_t
_dispatch_once_mark_done(dispatch_once_gate_t dgo)
{
return os_atomic_xchg(&dgo->dgo_once, DLOCK_ONCE_DONE, release);
}
Copy the code

How to destroy?

We slightly modified the internal implementation of the order:

+ (instancetype)shareSM {
    
    static SMObject *class = nil;
    static dispatch_once_t predicate;
    
    NSLog(@"1:%ld", predicate);
    predicate = 0;
    NSLog(@"1-1 :%ld", predicate);

    dispatch_once(&predicate, ^{
        NSLog(@"2:%ld", predicate);
        class = [[SMObject alloc] init];
    });
    
    NSLog(@"3:%ld", predicate);
    return class;
}
Copy the code

The log content shows that each time you change the value of the predicate, a new object is initialized. So, you can control how many times a singleton is initialized by setting the values of the predicate.

So, how is the dealocSM method implemented?

+ (void)deallocSM {

    predicate = 0;
    class = nil;
}
Copy the code

At the same time, these two parts need to be extracted from the shareSM and put into the class:

static SMObject *class = nil;
static dispatch_once_t predicate;
Copy the code

In this way, the destruction of singletons can be achieved.

Is it thread safe?

_dispatch_once_gate_tryenter(l)

static inline bool
_dispatch_once_gate_tryenter(dispatch_once_gate_t l)
{
	return os_atomic_cmpxchg(&l->dgo_once, DLOCK_ONCE_UNLOCKED,
			(uintptr_t)_dispatch_lock_value_for_self(), relaxed);
}
Copy the code

Here are the atomic operations, the handling of the lock, and the control of the threaded operations. _dispatch_lock_value_for_self locks the thread space in its current queue to prevent multithreading. To ensure thread security, the thread is locked to ensure that the current task is executed uniquely, preventing the same onceToken from being executed multiple times. It’s the encapsulation of multiple threads.