Small knowledge, big challenge! This paper is participating in theEssentials for programmers”Creative activities

Thread is mentioned in apple’s documentationrunloopThis chart is also provided by the governmentThe corresponding callbacks for these transaction Items are:

  • Block application:__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
  • Call timer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
  • Response source0:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
  • Source1 response:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
  • GCD main queue:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
  • The observer source:__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

The role of the runloop

  • Keep the program running
  • Handles various events in the App (touches, timers, performSelector)
  • Save CPU resources, improve the performance of the program, something to wake up, nothing to rest.

Runloop source view

A runloop is not the same as a regular dead-loop. It keeps the program running continuously, handles various events in the APP (touches, timers, performSelector), saves CPU resources, and provides performance for the program: do things when you need to, and rest when you need to. Runloop is CFRunLoop, we find the underlying source code to check

void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0 e10.false);
        CHECK_FOR_FORK();
    } while(kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); }Copy the code

1.0e10 = 10 ^10, this is the runloop timeout setting, set to a very large value, so it will give the illusion that it is always running. CFRunLoopGetCurrent -> _CFRunLoopGet0 runloop and thread binding. Thread and runloop are key-value binding regardless of whether the thread is the main thread

Dict [@"pthread_main_thread_np"] = mainLoop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
Copy the code

Take a look at its data structure, CFRunLoopRef

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* locked for accessing mode list */
    __CFPort _wakeUpPort;            // used for CFRunLoopWakeUp
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item* _blocks_head;
    struct _block_item* _blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};
Copy the code

There can be multiple _commonModeItem transactions under each of these modes.

Take a look at the structure of CFRunLoopModeRef

struct __CFRunLoopMode {
   // ...
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
	// ...
};
Copy the code

It just sums up in this pictureSo theseitemsHow does it depend on Mode being run in a runloop?

How does Runloop handle transactions

Let’s look at the Timer process. We know that adding a Timer transaction OC to the runloop is written like this

{
    CFRunLoopTimerContext context = {
        0,
        ((__bridge void *)self),
        NULL.NULL.NULL
    };
    CFRunLoopRef rlp = CFRunLoopGetCurrent(a);/** Parameter 1: memory used to allocate the object parameter 2: at what time is triggered (distance now) parameter 3: at what time is triggered parameter 4: future parameter parameter 5 :CFRunLoopObserver priority When there are multiple CfrunloopObservers in the same Runloop phase, this is where I would normally use parameter 0. Parameter 6: callback, such as triggering events. Parameter 7: context log information */
    CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1.0.0, lgRunLoopTimerCallBack, &context);
    CFRunLoopAddTimer(rlp, timerRef, kCFRunLoopDefaultMode);
}
Copy the code

Open CFRunLoop source code

static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
    CFStringRef modeName = (CFStringRef)value;
    CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
    CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
    if (CFGetTypeID(item) == __kCFRunLoopSourceTypeID) {
        CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
    } else if (CFGetTypeID(item) == __kCFRunLoopObserverTypeID) {
        CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
    } else if(CFGetTypeID(item) == __kCFRunLoopTimerTypeID) { CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName); }}Copy the code

ModeName == kCFRunLoopCommonModes and rL ->_commonModes If it is not empty, it adds the current Timer transaction and the corresponding callback function __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__. When will this callback be called? This brings us to the CFRunLoopRun function mentioned above, followed by the CFRunLoopRunSpecific function, which takes one step before entering the runLoop and one step before exiting the runLoop

Find the code associated with Timer transactions in __CFRunLoopRun

You can see in __CFRunLoopDoTimers iterating over timers in all modes and executing __CFRunLoopDoTimer

__CFRunLoopDoTimerMethod, find the corresponding RLM, which is RLMCFRunLoopModeRef, and then unlocks runloop to perform the callback

So the overall process is CFRunLoopRunSpecific-> __CFRunLoopRun -> __cfrunlooptimers -> __CFRunLoopDoTimer -> __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ The general process of other transactions is the same

Runloop principle

It was mentioned in the analysis of Timer above

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5), // Lifecycle
    kCFRunLoopAfterWaiting = (1UL << 6),  // Lifecycle
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Copy the code

The main processing logic is in **__CFRunLoopRun**, in a do-while loop

  • Notify Observers that timer events are about to be processed
  • Notify Observers that Source events are about to be processed
  • Processing blocks
  • Process source0: If source0=true, process blocks
  • Check if there is a port message (source1), then wake up runloop. Source1 has the wake up function of Runloop
  • Notify Observers: threads are about to hibernate

Sum up is the following picture, I believe we are familiar with.How runloop can be used in real projects will be covered in the next article, “Interface Optimization.”