Runloop is a basic component that is tied to threads and is behind many of the thread-related functionality. This article summarizes runloop from the following aspects:

  • What is the runloop
  • The role of the runloop
  • Runloop and thread relationship
  • Runloop and source code analysis
  • Analysis of runloop principle
  • Runloop application

What is the runloop

Runloop Official Apple document address

  • Runloop is what it sounds like. It’s basically a loop, but it’s an advanced loop. Usually do.. A while loop causes the CPU to wait in a busy state, while a Runloop is an “idle” wait.

Runloop’s run method is a do.. The while loop

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
  • When no event occurs, the Runloop goes to sleep. When an event occurs, the Runloop goes to the corresponding Handler to handle the event. Runloop keeps threads busy when they need to do something, and puts them to sleep when they don’t.
  • runloopIs actually an object that provides an entry function.

The role of the runloop

  • Keep the program running continuously and loop to avoid thread destruction
  • Handles various events in the APP (touches, timers, performSelector)
  • Save CPU resources, provider performance (do what you do, rest when you rest)

The use of Runloop in the system

On iOS, the following methods are available using runloop, and the stack can be viewed from a breakpoint to see the name of the called method:

  • 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

  • In response to source1: 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

Breakpoint to view runloop information

timer
block
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info) {
    if (func) {
        func(timer, info);
    }
    getpid(); // thwart tail-call optimization
}
Copy the code

The other runloop method names for the calls summarized above can be viewed using this debugging approach.

Runloop and thread relationship

  1. Runloops and threads correspond one to one
  2. Runloops are created when the thread first gets them and destroyed when the thread ends
  3. The main thread starts runloop by default, and the child thread starts it manually (when the program starts, the main thread runloop is started,[[NSRunLoop currentRunLoop] run])

input source
timer source

Get runloop

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    if(! __main) __main = _CFRunLoopGet0(pthread_main_thread_np());// no CAS needed
    return __main;
}
Copy the code

The source code calls _CFRunLoopGet0(), which passes the main thread pthread_main_thread_np(), which is defined as the main thread below

#if DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_IPHONESIMULATOR
CF_EXPORT pthread_t _CF_pthread_main_thread_np(void);
#define pthread_main_thread_np() _CF_pthread_main_thread_np()
Copy the code

Another way to get the current thread runloop is to call _CFRunLoopGet0 again, but pass in the current thread pthread_self().

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}
Copy the code

The thread runloop function _CFRunLoopGet0(both main thread and child thread) is used to get the thread runloop

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
    // Get the runloop by thread
	t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    // If the dictionary storing RunLoop does not exist
    if(! __CFRunLoops) { __CFSpinUnlock(&loopsLock); CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault,0.NULL, &kCFTypeDictionaryValueCallBacks);
	 // Create the main thread RunLoop
	CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
	CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
	if(! OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
	    CFRelease(dict);
	}
	CFRelease(mainLoop);
        __CFSpinLock(&loopsLock);
    }
    // find runloop in the dictionary
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    if(! loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFSpinLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));if(! loop) { CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } __CFSpinUnlock(&loopsLock); CFRelease(newLoop); }if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS- 1), (void(*) (void*))__CFFinalizeRunLoop); }}Copy the code
  • If the currently stored dictionary container does not exist, a container CFMutableDictionaryRef mutable dictionary is first created

  • RunloopCFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());

  • CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); Save the main thread and its runloop as key-value in the CFMutableDictionaryRef container

  • The first time it comes in, either the getMainRunloop or the runloop of the get child thread, the main thread runloop is always created

  • CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); You can use threads to retrieve runloops stored in dictionaries

  • If runloop is not found in the dictionary, a new Runloop object is created based on the current child thread and stored in the dictionary

  • If (pthread_equal(t, pthread_self())) {… } Determine if the current thread is passed in, create a callback if so, and destroy the current runloop if the thread is destroyed

  • This validates conclusions 1 and 2 above: Runloops and threads are one-to-one (dictionary save). The runloop is created when it is first fetched by the thread (and always creates the main thread runloop regardless of whether the main thread runloop or child thread runloop is fetched) and is destroyed (via callback destruction) at the end of the thread.

Runloop code validation

At the AppDelegate interrupt point, you can see that the main thread calls the __CFRunloopRun method, which proves the conclusion above: The main thread has runloop enabled by default! [] p1-jj.byteimg.com/tos-cn-i-t2…). Test the runloop code as follows

- (vod)viewDidLoad {
    super viewDidLoad];
DLThread *thread = [[DLThread alloc]initWithBlock:^{
       NSLog(@"%@",[NSThread currentThread]);
       [NSTimer scheduledTimerWithTimeInterval:1repeats:YES block:^(NSTimer * _Nonnul) {
            NSLog(@"timer");
        }];
        
    }];
    thread.name = @"Test";
    [thread start];
Copy the code

The only code in dlThread. m is the following

- (void)dealloc{
    NSLog(Thread destroyed);
}
Copy the code

If the timer does not print, it indicates that the child thread failed to start the timer. Then add the code to run the current thread’s runloop, as shown below:

DLThread *thread = [[DLThread alloc] initWithBlock:^{
        NSLog(@ "% @"[NSThread currentThread]);
        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"timer");
        }];
        [[NSRunLoop currentRunLoop] run];
    }];
    thread.name = @"Test";
    [thread start];
Copy the code

It is found that the timer has been printing, which proves two conclusions: the operation of timer is related to runloop, and the runloop of child thread needs to be opened manually

So how do you stop the timer? Added a flag value isStopping to exit the thread

DLThread *thread = [[DLThread alloc] initWithBlock:^{
        NSLog(@ "% @"[NSThread currentThread]);
        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"timer");
            if(self.isStopping){
                [NSThreadexit]; }}]; [[NSRunLoop currentRunLoop] run];
    }];
    thread.name = @"Test";
    [thread start];
Copy the code

After the thread is destroyed, the timer also stops, which partially confirms the conclusion 2 above: The runloop is destroyed at the end of the thread

Runloop source code analysis

Click to download runloop source code: password 3kww

To explore in the Runloop source code:

  • CFRunLoop
  • CFRunLoopMode
  • CFRunLoopSource
  • CFRunLoopObserver
  • CFRunLoopTimer

CFRunLoop

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;			/* locked for accessing mode list */
    __CFPort _wakeUpPort;			Used for CFRunLoopWakeUp The kernel sends a message to this port to wake up the runloop
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop 
    pthread_t _pthread;  // The thread corresponding to RunLoop
    uint32_t _winthread; //
    CFMutableSetRef _commonModes; // Stores a string, recording all modes marked common
    CFMutableSetRef _commonModeItems;// Store all commonMode items (source, timer, observer)
    CFRunLoopModeRef _currentMode; // The current running mode
    CFMutableSetRef _modes;   // CFRunLoopModeRef is stored
    struct _block_item* _blocks_head; //doblocks
    struct _block_item* _blocks_tail;
    CFTypeRef _counterpart;
};
Copy the code

Runloop is a structure that contains one thread, one currently running mode, N modes, and N commonModes.

  • Runloops correspond to threads one by one
  • Observers Runloop contains multiple modes, and modes contain multiple mode items (sources,timers,observers)
  • Runloop can only run one model at a time:
    • Switch mode: Stop loop -> Set mode -> restart runloop
    • Runloop filters the events to be processed by switching mode so that they do not affect each other
    • The key to iOS running smoothly

CFRunLoopMode

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;	/* must have the run loop locked before locking this */
    CFStringRef _name; / / the name of the mode
    Boolean _stopped;  // Whether mode is terminated
    char _padding[3];
    CFMutableSetRef _sources0; //sources0
    CFMutableSetRef _sources1;  //sources1
    CFMutableArrayRef _observers; / / notice
    CFMutableArrayRef _timers;  / / timer
    CFMutableDictionaryRef _portToV1SourceMap; // the dictionary key is mach_port_t and value is CFRunLoopSourceRef
    __CFPortSet _portSet;    _wakeUpPort and _timerPort are stored in this array
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};
Copy the code

A CFRunLoopMode object has a name, N source0s, N source1s, timers, observers, and ports. Visible events are managed by Mode, while RunLoop manages Mode.

The relationship between them is shown below:

Modes are customizable, but must contain at least one Mode item (source/ Timer/Observer). The same mode item can be held by multiple modes

There are three RunLoop modes available from Apple:

  • NSDefaultRunLoopMode(kCFRunloopDefaultMode): The default state in which the app normally runs
  • UITrackingRunLoopMode: interface tracking mode (e.g. sliding scrollView is not affected by other modes)
  • NSRunLoopCommonModes(kcfrunLoopAddCommonModes): is a set of the first two modes. You can add custom modes to the set using the cFRunLoopAddCommonModes function

There are two more modes that need to be understood:

  • GSEventReceiveRunLoopMode: receiving system internal mode, is usually not used
  • UIInitializationRunLoopMode: private, only used in app is started, the use is not in the collection

CFRunLoopSource

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits; Wait Signaled (3) //// are Signaled, and Source0 are released from their wait only when they are Signaled
    pthread_mutex_t _lock;
    CFIndex _order;			/* immutable */
    CFMutableBagRef _runLoops;
    union {
	CFRunLoopSourceContext version0;	/* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;	/* immutable, except invalidation */
    } _context;
};
Copy the code

CFRunloopSourceRef CFRunloopSourceRef CFRunloopSourceRef CFRunloopSourceRef CFRunloopSourceRef CFRunloopSourceRef CFRunloopSourceRef CFRunloopSourceRef CFRunloopSourceRef CFRunloopSourceRef CFRunloopSourceRef CFRunloopSourceRef CFRunloopSourceRef CFRunloopSourceRef

Source0
typedef struct {
    CFIndex version;
    void *  info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
    Boolean (*equal)(const void *info1, const void *info2);
    CFHashCode  (*hash)(const void *info);
    void    (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
    void    (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
    void    (*perform)(void *info);
} CFRunLoopSourceContext;
Copy the code

Source0: processes events within the App and manages them (triggers) by the App itself. Example :UIEvent CFSocket. You’ll almost always see it at the point of interruption.

  • Source0 is non-port-based. Contains only one callback (function pointer), which does not actively fire events.

  • CFRunLoopSourceSignal (source) marks this event as pending

  • CFRunLoopWakeUp wakes the Runloop to handle the event

Custom source implementation steps:

  1. Create an underlying source0 sourceCFRunLoopSourceRef source0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
  2. Add our created source0 to runloopCFRunLoopAddSource(rlp, source0, kCFRunLoopDefaultMode)
  3. Execute the signal and mark it for processingCFRunLoopSourceSignal
  4. Wake up the runloop to processCFRunLoopWakeUp
  5. Canceling removing a sourceCFRunLoopRemoveSource
  6. Release the runloopCFRelease(rlp)
Source1
typedef struct {
    CFIndex version;
    void *  info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
    Boolean (*equal)(const void *info1, const void *info2);
    CFHashCode  (*hash)(const void *info);
#if(TARGET_OS_MAC && ! (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
    mach_port_t (*getPort)(void *info);
    void *  (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
    void *  (*getPort)(void *info);
    void    (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;
Copy the code

source1:

  • Managed by runloop and Mach port, the Mach port driver contains a mach_port and a callback (function pointer) that is used to send messages to each other through the kernel and other threads.

  • It can actively wake up runloops (managed by the operating system kernel, e.g. CFMachPort,CFMessagePort)

  • You are also allowed to implement your own Source, but this is generally not done

CFRunLoopObserver

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFIndex _rlCount;
    CFOptionFlags _activities;		/* immutable */
    CFIndex _order;			/* immutable */
    CFRunLoopObserverCallBack _callout;	/* immutable */
    CFRunLoopObserverContext _context;	/* immutable, except invalidation */
};
Copy the code

It is an observer that listens for state changes in the Runloop, reports state changes externally, and triggers many mechanisms in the framework (such as CAAnimation).

Cfrunloop. h = cfrunloop. h = cfrunloop. h = cfrunloop. h

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

This corresponds exactly to the observer in the runloop flow below:

CFRunLoopTimer

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;		/* immutable */
    CFTimeInterval _tolerance;          /* mutable */
    uint64_t _fireTSR;			/* TSR units */
    CFIndex _order;			/* immutable */
    CFRunLoopTimerCallBack _callout;	/* immutable */
    CFRunLoopTimerContext _context;	/* immutable, except invalidation */
};
Copy the code
  • CFRunLoopTimerIs a timer that can throw a callback at a set point in time
  • CFRunLoopTimerandNSTimerIt’s toll-free bridged and can be exchanged
  • CFRunLoopTimerThere are three types of wrapper: NSTimer,performSelector, and CADisplayLink
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti   
invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti 
 invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument  
 afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;

+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;
Copy the code

The three types of timer are summarized as follows:

Analysis of runloop principle

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); }SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
Copy the code

Both CFRunLoopRun and CFRunLoopRunInMode call the CFRunLoopRunSpecific function

CFRunLoopRunSpecific

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    
    // select a mode based on modeName
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    
    
    /// Notify Observers: RunLoop is about to enter loop.
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    /// inner function, enter loop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    /// Notify Observers: RunLoop is about to exit.
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    return result;
}
Copy the code

The above source code is simplified after the source code, the actual source code is more complex, according to the source code can be concluded as follows:

  • Notify the Observer before entering the Run loop that the status is kCFRunLoopEntry
  • Notify the Observer after exiting the Run loop that the status is kCFRunLoopExit
  • Called when entering runloop__CFRunLoopRunfunction

__CFRunLoopRun(Core focus)

/// the core function
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    int32_t retVal = 0;
    do {
        
        /// Notify Observers that timer events are about to be processed
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        /// Notify Observers that Source events are about to be processed
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
        
        / / / processing Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        / / / sources0 processing
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        
        /// process sources0 and return YES
        if (sourceHandledThisLoop) {
            / / / processing Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        
        // check whether there is a port message (Source1)
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            /// process the message
            goto handle_msg;
        }
        
        /// Notify Observers that they are about to enter hibernation
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        
        /// wait to be awakened
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        
        
        // user callouts now OK again
        __CFRunLoopUnsetSleeping(rl);
        
        /// Notify Observers: they are astounded and end hibernation
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg:
        if(Awakened by Timer) {/ / / deal with Timers__CFRunLoopDoTimers (rl, RLM, mach_absolute_time ()); }else if(awakened by GCD) {/ / / handle the GCD
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else if(woke up by Source1) {/// wake up from Source1 and process Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
        }
        
        / / / processing block
        __CFRunLoopDoBlocks(rl, rlm);
        
        
        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if(__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { retVal = kCFRunLoopRunFinished; }}while (0 == retVal);
    
    return retVal;
}
Copy the code

The password is 3kww. __CFRunLoopServiceMachPort is also a function that listens for wake up port messages. The kernel will suspend this thread. Stay in the mach_MSG_trap state and wait for a message to accept mach_port(the port to wake up). The thread will go to sleep until it is woken up by another thread or another thread of another process sending a mach_MSG message to the port

__CFRunLoopServiceMachPort

/** * receives messages from the specified kernel port ** @param port Port that receives messages * @param buffer message buffer * @param buffer_size Message buffer size * @param livePort MSG -> msGH_LOCAL_port if the message is received successfully; MACH_PORT_NULL * @param timeout If the message is timed out, the unit is ms. * * @return Returns true on success and false */ in other cases
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout) {
    Boolean originalBuffer = true;
    kern_return_t ret = KERN_SUCCESS;
    for (;;) {		/* In that sleep of death what nightmares may come ... * /
        mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
        msg->msgh_bits = 0; // The flag bit of the header
        msg->msgh_local_port = port; // Source (sent message) or destination (received message)
        msg->msgh_remote_port = MACH_PORT_NULL; // Destination (sent message) or source (received message)
        msg->msgh_size = buffer_size;// Message buffer size, in bytes
        msg->msgh_id = 0;
        if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
        // Messages sent or received through mach_msg are Pointers,
        // If the message body is sent or received directly, memory replication will occur frequently, resulting in performance loss
        // So XNU uses a single kernel approach to solve this problem. All kernel components share the same address space, so only a pointer to the message is passedret = mach_msg(msg, MACH_RCV_MSG|MACH_RCV_LARGE|((TIMEOUT_INFINITY ! = timeout) ? MACH_RCV_TIMEOUT :0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);
        CFRUNLOOP_WAKEUP(ret);
        // Message received/sent successfully, set livePort to msgh_local_port
        if (MACH_MSG_SUCCESS == ret) {
            *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
            return true;
        }
         //MACH_RCV_TIMEOUT
        MACH_RCV_TIMED_OUT = MACH_RCV_TIMED_OUT
        // At this point, release the buffer and assign livePort to MACH_PORT_NULL
        if (MACH_RCV_TIMED_OUT == ret) {
            if(! originalBuffer)free(msg);
            *buffer = NULL;
            *livePort = MACH_PORT_NULL;
            return false;
        }
         //MACH_RCV_LARGE
        // If the receive buffer is too small, too large messages are queued and MACH_RCV_TOO_LARGE is returned in error,
        // In this case, only the header is returned and the caller can allocate more memory
        if(MACH_RCV_TOO_LARGE ! = ret)break;
          // Allocate more memory to buffer here
        buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
        if (originalBuffer) *buffer = NULL;
        originalBuffer = false;
        *buffer = realloc(*buffer, buffer_size);
    }
    HALT;
    return false;
}

Copy the code

Runloop application

Incident response

  • When a hardware event (touch/lock/shake/acceleration) occurs, the IOKit. Framework generates an IOHIDEvent event, which is accepted by the SpringBoard and forwarded by the Mach port to the required App process.

  • Apple registered a Source1 to accept the system events, triggered by the callback function Source0 (so the Event is actually based on Source0), call _UIApplicationHandleEventQueue () distribution within the application. _UIApplicationHandleEventQueue () will wrap IOHIDEvent process, and as a UIEvent processing or distribution, including identifying UIGesture/processing screen rotation/send UIWindow, etc.

Gesture recognition

  • When the above _UIApplicationHandleEventQueue () to identify a gesture, the first call will Cancel the current touchesBegin/Move/End series callback to interrupt. The system then marks the corresponding UIGestureRecognizer as pending.

  • Apple registered a Observer monitoring BeforeWaiting (Loop entering hibernation) events, the callback function Observer is _UIGestureRecognizerUpdateObserver (), Internally it gets all GestureRecognizer that has just been marked for processing and executes the GestureRecognizer callback.

  • This callback is handled when there are changes to the UIGestureRecognizer (create/destroy/state change).

Interface to refresh

  • When the UI changes (Frame changes, UIView/CALayer structural changes), or manually invoked the UIView/CALayer setNeedsLayout/setNeedsDisplay method, after the UIView/CALayer is marked as pending.

  • Apple has registered an Observer that listens for BeforeWaiting and Exit, and its callbacks traverse all pending UIViews /CALayer functions to perform the actual drawing and adjustment, and update the UI interface.

AutoreleasePool

  • The main thread Runloop registered the two Observers, the callback is _wrapRunloopWithAutoreleasePoolHandler

  • Observers1 listens for Entry events: has the highest priority and ensures that a release pool is created before all callbacks. Within the callback, _objc_autoreleasePoolPush() is called to create an automatic release pool

  • Observers2 listens for BeforeWaiting and Exit events: lowest priority, guaranteed to release the release pool after all callbacks. BeforeWaiting events: Call _objc_autoreleasePoolPop() and _objc_autoreleasePoolPush() torelease the old pool and create a new pool, Exit events: Call _objc_autoreleasePoolPop() torelease the automatic release pool

TableView delays loading images to ensure smooth

The ImageView loading method sets the current thread’s RunLoop mode to kCFRunLoopDefaultMode with PerformSelector, so that the loading method is not performed when sliding [self.imgView] performSelector:@selector(setImage:) withObject:cellImg afterDelay:0 inModes:@[NSDefaultRunLoopMode]];

Timer is not affected by ScrollView sliding

  • +timerWihtTimerInterval...Create a timer
  • [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]Add timer to the current runloop, using placeholder mode
  • runloop run/runUntilDataManually enable the child thread runloop
  • Timers created using GCD are not affected by RunLoop
// Get the queue
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // Create a timer (dispatch_source_t is essentially an OC object)
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0.0.queue);
    
    // Set various attributes of timer (when to start the task, how often to execute)
    // GCD time parameter, generally is nanosecond (1 second == 10 ^ 9 nanosecond)
    // The execution starts 1 second later than the current time
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (1.0 * NSEC_PER_SEC));
    
    // Execute every second
    uint64_t interval = (uint64_t) (1.0 * NSEC_PER_SEC);
    dispatch_source_set_timer(self.timer, start, interval, 0);
    
    // Set the callback
    dispatch_source_set_event_handler(self.timer, ^{
        NSLog(@"-- -- -- -- -- -- -- -- -- -- -- -- % @", [NSThread currentThread]);

    });
    
    // Start timer
    dispatch_resume(self.timer);
Copy the code

GCD

  • Dispatch_async (dispatch_get_main_queue) uses RunLoop

  • LibDispatch sends a message to the main thread Runloop to wake it up, takes a block from the message, and executes the block in the __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() callback

NSURLConnection

  • With NSURLConnection, you pass in a Delegate, and when [Connection start] is called, the Delegate will receive event callbacks.

  • Inside the start function, the CurrentRunLoop is retrieved and DefaultMode adds four source0s (sources that need to be triggered manually). CFMultiplexerSource handles the various Delegate callbacks, CFHTTPCookieStorage handles the various cookies.

  • When the network transmission, we can see NSURLConnection created two new thread: com. Apple. NSURLConnectionLoader and com. Apple. CFSocket. Private. The CFSocket thread processes the underlying socket connection. The NSURLConnectionLoader thread uses RunLoop internally to receive events from the underlying socket and notify the upper Delegate via the previously added Source0.

AFNetworking

  • Start the resident thread with runloop
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [runloop run];
Copy the code
  • Add runloop[NSMachPort port](source1)If runloop does not exit, no message is actually sent to the port

AsyncDisplayKit

The QuartzCore/UIKit framework implements a similar interface update mechanism: Add an Observer to the RunLoop of the main thread, listen for kCFRunLoopBeforeWaiting and kCFRunLoopExit events, and, upon receiving a callback, iterate over all previously queued tasks and execute them.

Caton detection

  • “Dispatch_semaphore_t” is a semaphore mechanism that dispatch_semaphore_t is a semaphore mechanism that dispatch_semaphore_t is a semaphore mechanism that dispatch_semaphore_t is a semaphore mechanism that dispatch_semaphore_t is a semaphore mechanism that dispatch_semaphore_t is a semaphore mechanism. GCD semaphore -dispatch_semaphore_t

  • Stalling is detected by listening for the state of the mainRunloop and the characteristics of the semaphore blocking thread, and stack information is logged by kCFRunLoopBeforeSource and kCFRunLoopBeforeWaiting intervals exceeding custom thresholds.

  • RunLoop Combat: Real-time caton monitoring

FPS testing

  • When you create a CADisplayLink object, you specify a selector that adds the CADisplayLink object to the Runloop, so you can call a method with the frequency of a screen refresh.

  • The FPS is calculated by counting the number of executions in the called method and dividing the number by the time.

  • Note: Normal refresh rate of iOS is 60 times per second.

@implementation ViewController {
    UILabel *_fpsLbe;
    
    CADisplayLink *_link;
    NSTimeInterval _lastTime;
    float _fps;
}

- (void)startMonitoring {
    if (_link) {
        [_link removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
        [_link invalidate];
        _link = nil;
    }
    _link = [CADisplayLink displayLinkWithTarget:self selector:@selector(fpsDisplayLinkAction:)];
    [_link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

- (void)fpsDisplayLinkAction:(CADisplayLink *)link {
    if (_lastTime == 0) {
        _lastTime = link.timestamp;
        return;
    }
    
    self.count++;
    NSTimeInterval delta = link.timestamp - _lastTime;
    if (delta < 1) return;
    _lastTime = link.timestamp;
    _fps = _count / delta;
    NSLog(@"count = %d, delta = %f,_lastTime = %f, _fps = %.0f",_count, delta, _lastTime, _fps);
    self.count = 0;
    _fpsLbe.text = [NSString stringWithFormat:@"FPS:%.0f",_fps];
}
Copy the code

Anti-collapse treatment

  • NSSetUncaughtExceptionHandler(&HandleException); Abnormal SIGILL signal monitoring, SIGTRAP, SIGABRT, SIGBUS, SIGSEGV, SIGFPE

  • Create a Runloop within the callback method that takes all runmodes from the main thread as an alternative to the application’s main Runloop.

CFRunLoopRef runloop = CFRunLoopGetCurrent(a);CFArrayRef allModesRef = CFRunLoopCopyAllModes(runloop);

while (captor.needKeepAlive) {
    for (NSString *mode in (__bridge NSArray *)allModesRef) {
        if ([mode isEqualToString:(NSString *)kCFRunLoopCommonModes]) {
            continue;
        }
        CFStringRef modeRef  = (__bridge CFStringRef)mode;
        CFRunLoopRunInMode(modeRef, keepAliveReloadRenderingInterval, false); }}Copy the code
  • You can record stack information, upload the server, or pop up a friendly prompt page and a series of operations.

Permanent thread

You can add your own threads to the Runloop to perform frequently processed tasks, such as checking network status and periodically uploading information.

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [self.thread start];
}

- (void)run
{
    NSLog(@"----------run----%@"[NSThread currentThread]);
    @autoreleasepool{
    /* If you do not add this statement, you will notice that the runloop is dead when created, because the runloop will die immediately without CFRunLoopSourceRef event source input or timer. Add an NSport to the runloop by adding an event source, timer, or observer to keep the runloop from hanging */
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    
    Methods 1, 2,3 do the same thing and let runloop run indefinitely
    [[NSRunLoop currentRunLoop] run];
   }

    
    2 / / method
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    
    3 / / method
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
    
    NSLog(@ "-- -- -- -- -- -- -- -- --");
}

- (void)test
{
    NSLog(@"----------test----%@"[NSThread currentThread]);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
Copy the code

All the above are my personal data collection and partial understanding of Runloop, please correct any mistakes, welcome to discuss.