This is the 26th day of my participation in the August More Text Challenge

RunLoop External interface

5 classes for RunLoop in Core Foundation

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

The CFRunLoopModeRef class is not exposed, but is encapsulated through the interface of CFRunLoopRef. Their relationship is as follows:

CFRunLoopModeRef

  • CFRunLoopModeRef represents the running mode of RunLoop. It is because RunLoop contains Source, Timer, and Observer that RunLoop keeps running. Otherwise, RunLoop will exit the loop

  • A RunLoop contains several modes, each of which contains several sources, timers, and observers

  • Each time RunLoop starts, only one of the modes can be specified, which is called CurrentMode

  • If you need to switch Mode, you can only exit RunLoop and re-specify a Mode to enter the loop

  • Each Mode can set its own Source, Timer, and Observer, so that they do not affect each other

  • There are 5 modes registered by default (Apple only opens the first two)

    • KCFRunLoopDefaultMode: The default Mode of the App in which the main thread normally runs
    • UITrackingRunLoopMode: interface tracing Mode, used by ScrollerView to track touch sliding, ensuring that the interface sliding is not affected by other modes
    • UIInitalizaationRunLoopMode: enter one of the first Mode when just start the App, start after the completion of the will no longer be used
    • GSEventReceiveRunLoopMode: accept system events internal Mode (graphics rendering, etc.), usually in less than
    • KCFRunLoopCommonModes: This is a placeholder Mode, not a real Mode

CFRunLoopTimerRef

  • CFRunLoopTimerRef (CFRunLoopTimerRef) is a time-based trigger.

    Eg: Add a TextView to the UI and slide the TextView to see if the timer executes

    • A:
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    
        // Add timer to the current RunLoop
        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
    }
    
    - (void)run{
        NSLog(@"run");
    }
    Copy the code

    Call scheduledTimerWithTimeInterval: target: the selector: the userInfo: repeats: method, returns Timer, system has automatically add the Timer to the RunLoop, And by default NSDefaultRunLoopMode, run is executed every 1s. But when you slide the textView, the timer will expire, the run method will stop executing, and when you stop sliding, the Run method will continue executing.

    When the Timer is added to the current RunLoop, the run method executes whether or not you slide the textView.

    • Method 2:
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; 
    
    // Run only in NSDefaultRunLoopMode
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    
    // run only in UITrackingRunLoopMode
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    
    // Only run in NSRunLoopCommonModes
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    Copy the code

    Call timerWithTimeInterval: target: the selector: the userInfo: repeats: method, returns timer, the timer is complete initialization, do not perform timing task, need to manually add the timer to the Runloop.

    1. It only runs in NSDefaultRunLoopMode. When you slide textView, the timer will no longer execute the timed task (the run method does not). Stop sliding and the timer will start executing the timed task.

    2. – only runs in UITrackingRunLoopMode. When you slide the textView, the timer will execute a timed task (run does). When you don’t slide the textView, the timer will not execute a timed task (Run does not).

    3. NSRunLoopCommonModes only. Whether you slide textView or not, the timer will perform timed tasks (run).

CFRunLoopSourceRef

CFRunLoopSourceRef Indicates the input source of the event. According to the function call stack, Source has two types

  • Source0: contains only one callback (function pointer), which does not actively fire events. To use it, you need to call it firstCFRunLoopSourceSignal(source), mark the Source as pending and call it manuallyCFRunLoopWakeUp(runloop)To wake up RunLoop to handle this event.
  • Source1: contains a mach_port and a callback (function pointer) that is used to send messages to each other through the kernel and other threads. This Source actively wakes up the RunLoop thread.

For example, a click event is first wrapped as an event and then transmitted to Source1, which is then distributed to Source0 for processing

CFRunLoopObserverRef

CFRunLoopObserverRef is the Observer, and each Observer contains a callback (function pointer) that the Observer receives when the state of the RunLoop changes

You can listen at the following points:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),          // About to enter Runloop
    kCFRunLoopBeforeTimers = (1UL << 1),   // Timer is about to be processed
    kCFRunLoopBeforeSources = (1UL << 2),  // The Source event will be processed soon
    kCFRunLoopBeforeWaiting = (1UL << 5),  // About to go to sleep
    kCFRunLoopAfterWaiting = (1UL << 6),   // Wake up from sleep
    kCFRunLoopExit = (1UL << 7),           // Exit runloop
    kCFRunLoopAllActivities = 0x0FFFFFFFU  // Monitor all states
};
Copy the code

If you add an Observer to a RunLoop, you can only use CF functions, which are generally used to intercept system events, etc

/* Add Observer parameter 1: default parameter 2: listen on activities 3: repeat parameter 4:0 */
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    NSLog(@"-- listen for Runloop status changes --%lu",activity);
});


/* Add an observer to listen for Runloop status parameters in kCFRunLoopDefaultMode: 1: current Runloop parameters 2: Listener parameters 3: mode */
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    
/ / release the Observer
CFRelease(observer);
Copy the code
  • CoreFoundation memory management
    • Anyone with aCreate,copy,retainAnd so on the words of the function, create the object, all need to do the last timerelease
    • releaseFunction:CFRelease(observer)

Internal logic of RunLoop

// Start with DefaultMode
void CFRunLoopRun(void) {
    CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0 e10.false);
}

// Start with the specified Mode, which allows setting RunLoop timeout
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

/ / RunLoop implementation
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
    
    // Find the corresponding mode according to modeName
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
    // If there is no source/timer/observer in mode, return it directly.
    if (__CFRunLoopModeIsEmpty(currentMode)) return;
    
    //1. Notify Observers: RunLoop is about to enter loop.
    __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
    
    // Inner function, enter loop
    __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
    
        Boolean sourceHandledThisLoop = NO;
        int retVal = 0;
        do {
        
            // 2. Notify Observers: RunLoop is about to trigger a Timer callback.
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
            // 3. Notify Observers: RunLoop is about to trigger the Source0 (non-port) callback.
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
            // Execute the added block
            __CFRunLoopDoBlocks(runloop, currentMode);
            RunLoop triggers the Source0 (non-port) callback.
            sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
            // Execute the added block
            __CFRunLoopDoBlocks(runloop, currentMode);
            // 5. If there is a port based Source1 in ready state, process the Source1 directly and jump to processing the message.
            if (__Source0DidDispatchPortLastTime) {
                Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
                if (hasMsg) goto handle_msg;
            }
            
            // Notify Observers: threads of RunLoop are about to enter sleep.
            if(! sourceHandledThisLoop) { __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); }// 7. Call mach_msg and wait for the message to accept mach_port. The thread will sleep until it is awakened by one of the following events.
            // a port-based Source event.
            // a Timer is running out
            /// • RunLoop's own timeout is up
            /// • Manually awakened by what other caller
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
                mach_msg(msg, MACH_RCV_MSG, port); 
            }
            
            //8. Notify Observers: threads of RunLoop have just been awakened.
            __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
            
            // The message is received and processed.
            handle_msg:
            
            9.1 If a Timer runs out of time, trigger the Timer's callback.
            if (msg_is_timer) {
                __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
            }

            9.2 If a block is dispatched to main_queue, execute the block.
            else if (msg_is_dispatch) {
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            }
            
            //9.3 If a Source1 (port-based) event is emitted, handle the event
            else {
                CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
                sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);

                if(sourceHandledThisLoop) { mach_msg(reply, MACH_SEND_MSG, reply); }}// Execute the block added to the Loop
            __CFRunLoopDoBlocks(runloop, currentMode);

            if (sourceHandledThisLoop && stopAfterHandle) {
                // Enter the loop with an argument that returns after processing the event.
                retVal = kCFRunLoopRunHandledSource;
            } else if (timeout) {
                // The timeout of the passed parameter marker is exceeded
                retVal = kCFRunLoopRunTimedOut;
            } else if (__CFRunLoopIsStopped(runloop)) {
                // Was forced to stop by an external caller
                retVal = kCFRunLoopRunStopped;
            } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
                // No source/timer/observer
                retVal = kCFRunLoopRunFinished;
            }
            // If there is no timeout, mode is not available, and loop is not stopped, continue loop.
        } while (retVal == 0);
    }
    
    //10. Notify Observers: RunLoop is about to exit.
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
 }                     
Copy the code

As you can see, RunLoop is actually just such a function, with a do-while loop inside. When you call CFRunLoopRun(), the thread stays in the loop forever; This function does not return until it times out or is stopped manually.

With respect to the memory of a RunLoop, in a RunLoop, one RunLoop corresponds to one thread, and the auto-free pool is a collection of objects for the current thread that are freed once before kCFRunLoopBeforeWaiting and recreated at the next startup