If you create an empty project and run it, the App will not quit even if there is no other business code in it. Why? It’s because of the Runloop. So what exactly is Runloop and how does it work? Now we’re going to uncover its mystery.

Case analysis

ubiquitousRunloop

  • In the official documentation for Runloop, we can see that Runloop is a dead-loop model in which threads sleep after executing a task and wake up when a new task needs to be executed. As shown below:



    And the way to think about it is, when YOU do something,RunloopWill be transformed into the corresponding events to deal with.

  • Code analysis:

    - (void)sourceDemo{
        // 1. __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@" Heaven King covers earth Tiger");
        }];
      
        // 2. __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"hello word");
        });
      
        // 3. __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
        void (^block)(void) = ^ {NSLog(@ "123");
        };
      
        block();
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        // 4. __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
        NSLog(@" Here we go bro!!");
    }
    Copy the code

    Take the first (Timer) case for print analysis:



    The following conclusions can be drawn from the print stack:

      1. timerEvents in therunloopThe corresponding is__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__type
      1. FoundationIn theRunloopAt the bottom level, the actual corresponding isCoreFoundationIn theCFRunloop
  • There are six types of Runloop events:

      1. Block application:__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
      1. The timer application:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
      1. Response source0:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
      1. In response to source1:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
      1. The GCD master queue:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
      1. The source (the observer):__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

Differences from other loops

  • General cycle CPU:

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            while (1) {
                NSLog(@"hello");
            }
            return UIApplicationMain(argc, argv, nil.NSStringFromClass([AppDelegate class])); }}Copy the code

    After running, the CPU usage is about 30%



  • CPU occupied by Runloop:

    Run an empty project and findcpuAlways be0But the project has been running

Runloop summary: 1. Keep the application running continuously 2. Handle various events in the APP (touches, timers, performSelector) 3. Save CPU resources, provider performance: do what you do, rest when you do it

Is a Runloop an infinite loop and what kind of loop is it? Next we go into the world of source code

The source code interpretation

  • In the stack analysis above, we can get the following relationship:



    • RunloopAt the bottom isCFRunloop, we will analyze theCFRunloopanalysis
  • Download the CoreFoundation Source code in Source Browser and read the cfrunloop. c file

create

  • There is a CFRunLoopRun method in the source code:

    void CFRunLoopRun(void) {    /* DOES CALLOUT */
        int32_t result;
        do {
            result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0 e10.false);
            CHECK_FOR_FORK(a); }while(kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); }Copy the code
    • Mostly by judgmentresultThe results ofdo-whileCycle, so that means that this isrunloopCreation process
  • Let’s look at CFRunLoopGetCurrent, which is the function that gets the CFRunLoopRef:

    CFRunLoopRef CFRunLoopGetCurrent(void) {
        CHECK_FOR_FORK(a); CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);if (rl) return rl;
      
        return _CFRunLoopGet0(pthread_self());
    }
    Copy the code
    • Here we start by typeTSDTo obtainrl, returns if it exists, and returns if it does not exist based on the current thread_CFRunLoopGet0Function to obtain
  • _CFRunLoopGet0:



    • To obtainCFRunLoopRefThe process is relatively simple:
        1. Determine the current thread first. If it does not exist, set it to the primary thread
        1. judgeCFMutableDictionaryRefDoes it exist:
        • There is no: createCFMutableDictionaryRefAnd then inThe main threadcreateCFRunLoopRef, and then the main threadkey.mainloopforvalueIn the dictionary
        • There are: Based on the current threadCFRunLoopRefIf not, the current thread is not the main thread and is created based on the current threadkey.newLoopforvaluePut it in the dictionary,
      1. getloopAfter the call_CFSetTSDforstorage
  • The process for obtaining NSRunLoopRef is as follows:



  • From the above analysis, NSRunloopRef needs to be created either on the main thread or not, so what is the data structure of the runloop?

The data structure

  • Call the __CFRunLoopCreate function and see the creation and assignment process:



    • Loop members include model, item, and so on.



    • CFRunloop is a structure. _commonModes, _commonModeItems, and _modes are collection types.



    • CFRunLoopModeRef is a CFRunLoopMode structure. CFRunLoopModeRef is a CFRunLoopMode structure. Sources0, sources1, Obervers, timers are collections or arrays, that is, Runloop has multiple CFRunLoopMode, each model corresponds to multiple events, as shown in the following figure:



  • Now that we know there are many items in the runloop (source0, source1, timer, observer), what are they and how do they rely on model to execute in the runloop? Next, explore the underlying runloop

The underlying principle

  • In cfrunloop. h, we can see several types of add:



    • addthemodeThere are several types:
      • commonModetype:CFRunLoopAddCommonMode
      • blocktype:CFRunLoopPerformBlock
      • Sourcetype:CFRunLoopAddSource
      • Observertype:CFRunLoopAddObserver
      • Timertype:CFRunLoopAddTimer

1. Add events

  • CFRunLoopPerformBlock looks like this:



    • runloopThe following steps are taken to add events:
        1. Make sure thatmodelThere are
        1. createrunloopIn the_block_itemSingly linked list
        1. rightnew_itemAssign values to related parameters:
        • If the tail does not exist, it has not been addedmode, the header is set tonew_item;
        • If the tail exists, then the tail is setnextfornew_item
        • And then I set the end to benew_item
  • CFRunLoopAddCommonMode type:



    • This function is the core of __CFRunLoopAddItemsToCommonMode, its code is as follows:



      The originalcommonModeThe type corresponds to thetasource,observerandtimerThree kinds of events will be analyzed one by one below

  • CFRunLoopAddTimer type:





    • addtimerThe type ofmodeRelatively simple, there are mainly the following steps:
        1. To obtain the correspondingmodeNamethemode
        1. Make sure thatmodeIn thetimersThe array is
        1. iftimertherlModesThere’s no new in the arraymodethename, it is torlModesaddname
        1. willtimerDeposited in the correspondingmodelthetimersAn array of
  • CFRunLoopAddSource type:



    • sourceType to addmodewithtimerType similar:
        1. According to themodeNameGet the correspondingmode
        1. willsourceAssigned tomodeIn thesources0orsources1
        1. willrunloopAdded to thesourceIn therunLoopsIn the
  • CFRunLoopAddObserver type:



    • addObserverThe type ofmodeAlso similar to the above:
        1. According to themodeNameGet the correspondingmode
        1. inmodetheobserversArray traversal from back to front:
        • If you go throughobserver->orderLess than or equal to the currentobserver->order, it willobserverInsert into the currentindex+1place
        • If I go through it,observer->orderThey’re all less than what’s in the arrayobserver->order, will be newobserverInsert into the first position in the array

From the analysis of several processes of adding modes, we can perceive that the three processes of commonMode are similar, but the block mode is different, which is added in the form of one-way linked list.

2. Enter the loop

  • CFRunLoopRunSpecific (CFRunLoopRunSpecific);



      1. In the function, the basismodeNameGets the value of this runmodeIf not found, the loop is not entered
      1. willrunloopthecurrentModeSet cost times to runmode
      1. ifmodeA type ofentry,observernoticeRunloopIs about to enterloop
      1. call__CFRunLoopRunfunctionrun
      1. ifmodelA type ofexit,observernoticerunloopIs about to quit
  • The main code for __CFRunLoopRun is as follows:



    The main process is to judge the cycle conditions according to the state, and then process sleep and corresponding events in the cycle. The core process is as follows:



3. Event handling

    1. observersEvents:



    • To deal withobserversEvents mainly include the following steps:
        1. To obtainobserversThe array size
        1. Gets or creates one based on the sizeCFRunLoopObserverRefThe type ofcollectedObservers
        1. traversemode->observersAnd thecollectedObserversPerform correlation assignments
        1. Iterate over the new collection and call__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__Perform event processing

        Its implementation is as follows:

        static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__() __attribute__((noinline));
        static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
            if (func) {
                func(observer, activity, info);
            }
            asm __volatile__(""); // thwart tail-call optimization
        }
        Copy the code
    1. blocksEvents:



    • blockthemodeinrunloopIs in the form of a linked list, and its execution process is as follows:
        1. Gets the head and tail of the linked list
        1. Walk from the beginning node to the end node
        1. If the conditions are met, the command is executed__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__Function, and then executeblockThe callback

        Its core functions are as follows:

        static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(void (^block)(void)) {
            if (block) {
                block(a); }asm __volatile__(""); // thwart tail-call optimization
        }
        Copy the code
    1. timerEvents:



    performtimerThe event is traversed firstmodethetimersArray, will satisfy the conditiontimerPut in the new array and iterate through the execution__CFRunLoopDoTimerFunction:



    __CFRunLoopDoTimerThe function is executed if the condition is satisfied__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__Function to perform the callback

    1. mainQueueThe event



    mainQueueEvent is inrunloopAfter being woken up, then call__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__Function, and then call_dispatch_main_queue_callback_4CFFunction to deal withmsg

    1. source0Events:



    source0Event handling needs to determine the type:

    • If it isIDType, is executed after the corresponding judgment__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__function
    • If it is an array type, you need to iterate over the array, and getobserverExecute if conditions are met__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__function
    1. source1Events:

    The type of source1 is much simpler:



    observerIf yes, call__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__The function then performs the callback.