What is a RunLoop?

So what is a RunLoop

“Running laps” is just right

  • One of the things that keeps iOS apps responsive and running is that they have an Event Loop.

  • Event loop mechanism, that is, the thread can respond to and process the event at any time, this mechanism requires that the thread can not exit, and the efficient completion of event scheduling and processing.

  • The event loop mechanism is called a RunLoop

  • ==RunLoop is actually an object ==, which is used in the loop to handle various events (such as touch events, UI refresh times, timer times, Selector events, etc.) to keep the program running and to go to sleep when no events are being handled. Thus saving CPU resources and improving program performance.

By default, the main thread is RunLoop

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!");
    }
    return 0;
}


int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code

The UIApplicationMain function internally helps us turn on the RunLoop for the main thread. UIApplicationMain has an infinite loop of code inside it.

function loop() {
    initialize();
    do {
        var message = get_next_message();
        process_message(message);
    } while(message ! = quit); }Copy the code

The program will always run in a do-while loop

Take a look at apple’s official RunLoop model

A RunLoop is a loop in a thread. In the loop, a RunLoop constantly detects messages, waits for them through Input sources and Timer sources, then processes the received event notification thread, and takes a rest when there are no events.

RunLoop object

Get the RunLoop object

  • Introduce the RunLoop object
  • The Fundation framework (encapsulated based on CFRunLoopRef) NSRunLoop object
    • NSRunLoopIs based onCFRunLoopRefProvides object-oriented apis, but these apis are not thread-safe
[NSRunLoop currentRunLoop];// Get the current RunLoop object
[NSRunLoop mainRunLoop];// Get the RunLoop object for the main thread
Copy the code
  • CoreFoundation CFRunLoopRef object
    • CFRunLoopRefIs in theCoreFoundationFramework, which provides pure C function apis, all of which are thread-safe
CFRunLoopGetCurrent(a);// Get the current thread's RunLoop object
CFRunLoopGetMain(a);// Get the RunLoop object for the main thread
Copy the code

So the two ways you can do that are essentially

//Foundation
NSRunLoop *runLoop1 = [NSRunLoop currentRunLoop];
NSRunLoop *mainRunLoop1 = [NSRunLoop mainRunLoop];
//Core Foundation
CFRunLoopRef runLoop2 = CFRunLoopGetCurrent(a);CFRunLoopRef mainRunLoop2 = CFRunLoopGetMain(a);Copy the code

Take a look at the implementation of these two functions

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

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

They all call _CFRunLoopGet0, which we’ll see later

CFRunLoopRef source section (introduction thread dependent)

Take a look at the source code for CFRunLoopRef

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* locked for accessing mode list */__CFPort _wakeUpPort; [By this functionCFRunLoopWakeUpThe kernel sends a message to the port to wake up the runloop.volatile _per_run_data *_perRunData;              // reset for runs of the run looppthread_t _pthread; 【 uint32_t _winThread;CFMutableSetRef_commonModes; [Store string, record all mode marked as common]CFMutableSetRef_commonModeItems; Store all commonMode items (source, timer, observer)CFRunLoopModeRef_currentMode; [Current mode]CFMutableSetRef_modes; 【 Stored isCFRunLoopModeRefstruct_block_item *_blocks_head; 【doUse in blocksstruct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};
Copy the code
  • In addition to recording some properties, the focus is on the three member variables
pthread_t _pthread; [Thread corresponding to RunLoop]CFRunLoopModeRef_currentMode; [Current mode]CFMutableSetRef_modes; 【 Stored isCFRunLoopModeRefCopy the code

Which brings us to the following question

RunLoop and thread

Let’s see how _CFRunLoopGet0 is implemented. What does it have to do with runloops and threads

// Global Dictionary, key is pthread_t,value is CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
// Access the lock for __CFRunLoops
static CFSpinLock_t loopsLock = CFSpinLockInit;

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
//t==0 is a synonym for the always valid "main thread"

// Get the RunLoop corresponding to the pthread
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
    // If pthread is null, obtain the main thread
    t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    if(! __CFRunLoops) { __CFSpinUnlock(&loopsLock);// On the first entry, create a temporary dict dictionary
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0.NULL, &kCFTypeDictionaryValueCallBacks);
    // Get the RunLoop corresponding to the main thread passed in
    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
    // Save the main thread, and save the main thread -key and runloop-value into the dictionary
    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
    // Here the NULL and __CFRunLoops Pointers both point to NULL, so write the dict to __CFRunLoops
    if(! OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
    / / release the dict
        CFRelease(dict);
    }
    / / release mainRunLoop
    CFRelease(mainLoop);
        __CFSpinLock(&loopsLock);
    }
    // The runLoop of the main thread is always created on the first entry, whether it is getMainRunLoop or getsubthread runLoop

	// Get the RunLoop from the global dictionary
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    if(! loop) {// If not, create a new RunLoop
    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    // After the creation, the thread is the key and the runLoop is the value, and the dictionary is stored one to one. The next time the dictionary is retrieved, the runLoop is returned directly
    if(! loop) {// Insert newLoop into the dictionary __CFRunLoops, key is thread t
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFSpinUnlock(&loopsLock);
    CFRelease(newLoop);
    }

	// If the incoming thread is the current thread
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
        // Register a callback to destroy the corresponding RunLoop when the thread is destroyed
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS- 1), (void(*) (void*))__CFFinalizeRunLoop); }}return loop;
}
Copy the code

As you can see from this source code

  • Each thread has a unique RunLoop object that corresponds to it
  • The RunLoop is stored in a global Dictionary, with the thread as the key and RunLoop as the value
  • There is no RunLoop object when the thread is created. The RunLoop is created when the thread’s RunLoop is first acquired, and the RunLoop is destroyed when the thread ends
  • The RunLoop for the main thread is automatically obtained (created), and the child thread is not RunLoop enabled by default

Related classes for RunLoop

There are five classes associated with RunLoop

  1. CFRunLoopRefAn object that represents a RunLoop
  2. CFRunLoopModeRefRunLoop Running mode
  3. CFRunLoopSourceRefIs the input source (event source) mentioned in the RunLoop model diagram
  4. CFRunLoopTimerRefTiming sources
  5. CFRunLoopObserverRefObserver, listening for changes in the RunLoop state

  1. aRunLoopContains severalMode, eachModeThere are several moreSource/Timer/Observer

2. Each call to RunLoop’s main function can specify only one Mode. This Mode is called CurrentMode 3. If you need to switchModeCan only quitLoop, and specify another oneModeTo enter. This is mostly to separate the groupsSource/Timer/Observer4. If there is one modeSourcr/Timer/ObserverIf no, RunLoop exits without entering the loop

Implementation of related classes in RunLoop

A RunLoop contains several modes, and each Mode contains several sources/timers/observers

CFRunLoopModeRef

Again, CFRunLoopModeRef represents the running mode of a RunLoop

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;    /* must have the run loop locked before locking this */
    CFStringRef _name; // The name of the mode by which the mode is identified
    Boolean _stopped; // Whether mode is terminated
    char _padding[3];
    // The core part of the structure
------------------------------------------
    CFMutableSetRef _sources0;//Sources0
    CFMutableSetRef _sources1;//Sources1
    CFMutableArrayRef _observers;/ / observer
    CFMutableArrayRef _timers;/ / timer
------------------------------------------
    CFMutableDictionaryRef _portToV1SourceMap;// Dictionary key is mach_port_t, value is CFRunLoopSourceRef
    __CFPortSet _portSet;// Save all the ports to listen on, such as _wakeUpPort and _timerPort, 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
  • aCFRunLoopModeRefObjects have one name, severalsource0.source1.timer.observerandport, you can see that the events are all caused bymodeIn management, whileRunLoopManages theMode

Five modes of operation

The system has five modes registered by default

  1. kCFRunLoopDefaultMode: The default Mode in which the main thread normally runs
  2. UITrackingRunLoopMode: Interface tracking Mode, used for ScrollView tracking touch sliding, to ensure that the interface sliding is not affected by other modes
  3. UIInitializationRunLoopMode: The first Mode entered at the beginning of App startup. It will not be used after startup and will be switched to kCFRunLoopDefaultMode
  4. GSEventReceiveRunLoopMode:An internal Mode that accepts system events and is usually not used
  5. kCFRunLoopCommonModes: this is a placeholder Mode used to mark kCFRunLoopDefaultMode and UITrackingRunLoopMode, but not a real Mode

Among them, kCFRunLoopDefaultMode, UITrackingRunLoopMode and kCFRunLoopCommonModes are the modes we need to use in the development

CommonModes

In the RunLoop object, there is a previous concept called CommonModes

// Simplified version
struct __CFRunLoop {
    pthread_t _pthread;
    CFMutableSetRef _commonModes;// Stores a string, recording all modes marked as common
    CFMutableSetRef _commonModeItems;// Store all commonMode items (source, timer, observer)
    CFRunLoopModeRef _currentMode;// The current mode
    CFMutableSetRef _modes;// Stores a CFRunLoopModeRef object with different mode types and different mode names
};
Copy the code
  • A Mode can mark itself as a Common attribute by adding its ModeName to RunLoop’s commonModes.
  • Whenever the contents of the RunLoop change, the RunLoop will_commonModeItemsIn theSource/Observer/TimerSync to all modes with the Common tag. The underlying principle
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    __CFRunLoopLock(rl);
    if (!CFSetContainsValue(rl->_commonModes, modeName)) {
    // Get all _commonModeItems
    CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
    // Get all _commonModes
    CFSetAddValue(rl->_commonModes, modeName);
    if (NULL! = set) {CFTypeRef context[2] = {rl, modeName};
        Add all _commonModeItems to _commonModes for each Mode one by one
        CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
        CFRelease(set); }}else {
    }
    __CFRunLoopUnlock(rl);
}
Copy the code

Only the following two management Mode interfaces are exposed by CFRunLoop

CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRefmodeName, ...) ;Copy the code

What is a Mode Item? What types of elements does Mode contain?

  • RunLoopMessages that need to be processed, including time and source messages, they all belong toMode item
  • RunLoopIt can also be listened onobserverObject, also belongs toMode item
  • All of themode itemCan be added to Mode, which can contain more than onemode item, aitemMultiple modes can also be added. But aitemIt does not work when added to the same mode repeatedly. If there is no item in a mode, thenRunLoopWill exit, not enter the loop

  • modeThe exposedmode itemThe interfaces are as follows
CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
Copy the code
  • We can only do it by operationmode nameTo operate the internalmode, when you pass in a newmode namebutRunLoopThere is no internal counterpartmodeRunLoop will automatically create one for youCFRunLoopModeRef.
  • For aRunLoopIn terms of its internalmodeIt can only be added but cannot be deleted

Apple publicly provides two modes

  • kCFRunLoopDefaultMode(NSDefaultRunLoopMode) andUITrackingRunLoopModeYou can use these twoMode NameTo operate its corresponding Mode
  • Apple also provides a string that operates on the Common token: kCFRunLoopCommonModes (NSRunLoopCommonModes). You can use this string to operate on Common Items, or to mark a Mode to “Common”. Use this string to distinguish it from other mode names.

CFRunLoopSourceRef

Where events occur

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;   // Order of execution
    CFMutableBagRef _runLoops;// Contains multiple runloops
    / / version
    union {
    CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;    /* immutable, except invalidation */
    } _context;
};
Copy the code

Two versions are Source0 and Source1

  • Source0Contains only one callback (function pointer), which does not actively fire an event. To use, you need to first callCFRunLoopSourceSignal(source)Will thisSourceMark it as pending, and then call it manuallyCFRunLoopWakeUp(runloop)To awakenRunLoopAnd let it handle the event
  • Source1It contains amach_portAnd a callback (function pointer) that is used to send messages to each other through the kernel and other threads. This Source can be actively awakenedRunLoopThe thread.

So let’s just write a button click event and look at the call stack with thread backtrace and we can see where the click event comes from, right

  • First the program starts, calling main on line 18, which calls UIApplicationMain on line 17, and then all the way up to the click event on line 0
  • And at the same time we can see the call up hereSource0That is, our click event belongs to the Source0 function, and the click event is handled in Source0.

  • Source1, on the other hand, is used to receive, distribute, and then distribute system events to Source0 for processing

CFRunLoopTimerRef

Time-based triggers

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;// The mode set containing the timer
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;        /* immutable */
    CFTimeInterval _tolerance;          /* mutable */
    uint64_t _fireTSR;            /* TSR units */
    CFIndex _order;            /* immutable */
    CFRunLoopTimerCallBack _callout; / / timer callback
    CFRunLoopTimerContext _context;   // Context object
};
Copy the code
  • CFRunLoopTimerRefIt’s a time-based trigger. It’s the same asNSTimerYou can mix it up. It contains a length of time and a callback (function pointer). When it’s added toRunLoopWhen,RunLoopIt registers the corresponding point in time, and when the point in time arrives,RunLoopWill wake up to perform that callback

For NSTimer scheduledTimerWithTimeInterval and RunLoop relationship

[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
Copy the code

It will automatically join NSDefaultRunLoopMode

Both the same

NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
Copy the code

The timer is not sliding accurately

We must have experienced situations in our development where when we use the NSTimer to do something every time we slide the UIScrollView, the NSTimer will pause, and when we stop the slide, the NSTimer will resume

For example

To build a tableView

Adds the timer to NSDefaultRunLoopMode of the current RunLoopNormal: Print once a second

The timer stops when we drag and drop the tableView

And the reason for that is

  • When we’re not doing anything, RunLoop is inNSDefaultRunLoopModeUnder the
  • When we drag, the RunLoop endsNSDefaultRunLoopMode, switch toUITrackingRunLoopModeMode, this mode is not addedNSTimerSo ourNSTimerIt doesn’t work
  • When we release the mouse, RunLoop ends the UITrackingRunLoopMode mode and switches backNSDefaultRunLoopModePattern, soNSTimerIt’s back to normal

So what should we do to solve this problem?

Can’t we get NSTimer to work in both modes?

Using CommonModes solves this problem (which solves the question of what Common can do)

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Copy the code

At this point we can drag and drop without any problems

CFRunLoopObserverRef

CFRunLoopObserverRef is an Observer, and each Observer contains a callback (function pointer) that can be used to receive a change in the state of a RunLoop

typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;// Listen to the RunLoop
    CFIndex _rlCount;// Add the number of RunLoop objects for this Observer
    CFOptionFlags _activities;		/* immutable */
    CFIndex _order;// Only one listener can be monitored at a time
    CFRunLoopObserverCallBack _callout;// Listen callback
    CFRunLoopObserverContext _context;Context is used for memory management
};

// There are several time points for observation
typedef CF_OPTIONS(CFOptionFlags.CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),   // You are about to enter RunLoop
    kCFRunLoopBeforeTimers = (1UL << 1), The Timer is about to be processed
    kCFRunLoopBeforeSources = (1UL << 2), // Source is about to be processed
    kCFRunLoopBeforeWaiting = (1UL << 5), // It is about to go to sleep
    kCFRunLoopAfterWaiting = (1UL << 6),// Just woke up from hibernation
    kCFRunLoopExit = (1UL << 7),// Exit RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Copy the code

The internal logic of RunLoop

The internal logic of RunLoop is described as follows

The simplified __CFRunLoopRun function preserves the main code to see the implementation

[Start with DefaultMode]void CFRunLoopRun(void) {
    CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0 e10.false); } set RunLoop timeout with the specified Mode.int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); } [RunLoop implementation]int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false); If there is no source/timer/observer in mode, return:if (__CFRunLoopModeIsEmpty(currentMode)) return;
    
  【1.Inform Observers: RunLoop that the loop is about to enter. 】  __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry); __CFRunLoopRun(RunLoop, currentMode, seconds, returnAfterSourceHandled) {Boolean sourceHandledThisLoop =NO;
        int retVal = 0;
        do{【2.Inform Observers: RunLoop that a Timer callback is about to be triggered. 】  __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers); 【3.Inform Observers: RunLoop that the Source0 (non-port) callback is about to be triggered. 】  __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources); [Execute added block] __CFRunLoopDoBlocks(runloop, currentMode); 【4.RunLoop fires the Source0 (non-port) callback. 】  sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle); [Execute added block] __CFRunLoopDoBlocks(runloop, currentMode); 【5.If any Source1 (based on port) is in the ready state, process the Source1 directly and jump to process the message. 】if (__Source0DidDispatchPortLastTime) {
                Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
                if (hasMsg) gotohandle_msg; Inform Observers: RunLoop that the thread is about to go to sleep.if(! sourceHandledThisLoop) { __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); } 【7.Call mach_msg to wait for the message to receive mach_port. The thread goes to sleep until it is awakened by one of the following events. 】 • A port-based Source event. __CFRunLoopServiceMachPort(waitSet, & MSG,sizeof(msg_buffer), &livePort) {
                mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
            }
 
            【8.Inform Observers: RunLoop that the thread has just been awakened. 】  __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting); Receive the message, process the message. Handle_msg: 【9.1If a Timer is out of time, the Timer callback is triggered. 】if(msg_is_timer) {__CFRunLoopDoTimers(runloop, mach_absolute_time())} 【9.2If there is a block dispatch to main_queue, execute the block. 】else if (msg_is_dispatch) {
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            } 
 
            【 9.3If a Source1 (based on port) emits an event, 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); }} __CFRunLoopDoBlocks(runloop, currentMode);if(sourceHandledThisLoop && stopAfterHandle) {  retVal = kCFRunLoopRunHandledSource; }else ifRetVal = kCFRunLoopRunTimedOut; (timeout) {[timeout = kCFRunLoopRunTimedOut; }else if(__CFRunLoopIsStopped(runloop)) {[forced to stop by an external caller] retVal = kCFRunLoopRunStopped; }else if(__CFRunLoopModeIsEmpty(runloop, currentMode)) {[source/timer/observer none] retVal = kCFRunLoopRunFinished; If the mode is not available and the loop is not stopped, continue the loop. }while (retVal == 0);
    }
    
   【 10.Inform Observers: RunLoop that they are about to exit. 】  __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); }Copy the code

In fact, RunLoop is one such function, with a do-while loop inside. When you call CFRunLoopRun(), the thread stays in the loop until it times out or is called manually

RunLoop callback

  • When the App is started, the system will register five modes by default.
  • When a RunLoop makes a callback, it’s usually called out by a long function call, which you’ll usually see on the call stack when you’re debugging your code for breakpoints. Here’s how RunLoop works:
{
    /// 1. Inform Observers that they are about to enter RunLoop
    /// An Observer will create AutoreleasePool: _objc_autoreleasePoolPush();
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
    do {
 
        /// 2. Inform Observers: The Timer callback is about to be triggered.
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
        Inform Observers: Source (non-port-based,Source0) callbacks are about to be triggered.
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
 
        /// 4. Trigger the Source0 (non-port based) callback.
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
 
        /// 6. Inform Observers, they are about to go to sleep
        /// Create an AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
 
        /// 7. sleep to wait msg.
        mach_msg() -> mach_msg_trap();
        
 
        /// 8. Inform Observers that the thread is awake
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
 
        /// 9. If the Timer wakes up, call back the Timer
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
 
        /// 9. If the queue is awakened by dispatch, execute all blocks that are placed in the main queue by calling dispatch_async
        __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
 
        /// 9. If Runloop is awakened by a Source1 (port-based) event, handle this event
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
 
 
    } while(...). ;/// 10. Inform Observers that they are about to exit RunLoop
    /// There is an Observer that releases AutoreleasePool: _objc_autoreleasePoolPop();
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
}
Copy the code

The instance test

The use of NSTimer

There is about

ImageView display delay

When the interface has UITableView, and each UITableViewCell has a picture in it. This is when we scroll through the UITableView, if we have a bunch of images to display, we might get stuck.

How to solve this problem?

We should delay the implementation of the image, that is, the ImageView delays the display of the image. Don’t load the image when we slide, drag it to the end of the display

[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"imgName.png"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]].Copy the code

The user clicks on the screen, and in the main thread, the image is displayed after three seconds, but after the user clicks on the screen, if the user starts scrolling textView again, then even after three seconds, the image will not be displayed. When the user stops scrolling, the image will be displayed.

This is because the method setImage is limited to NSDefaultRunLoopMode mode. While scrolling textView, the program is running in tracking mode, so the setImage method does not execute.

Permanent thread

If background operations are frequent during application development, such as playing music in the background, downloading files, etc., we want this thread to stay in memory forever

We can add a strong reference child thread for resident memory, add a source under the RunLoop of that thread, and turn RunLoop on

@property (nonatomic.strong) NSThread *thread;
Copy the code
 self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run1) object:nil];
    [self.thread start];
}

- (void)run1 {
    NSLog(@"----run1-----");

    /* If you do not add this sentence, you will find that the runloop is not created, because the runloop will die immediately if there is no CFRunLoopSourceRef event source input or timer. The following method to add an NSport to a Runloop is to add an event source, and you can also add a timer or observer to keep the runloop from dying */

        [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    // Methods 1,2, and 3 implement the same effect, allowing the runloop to run indefinitely
    2 / / method
// [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    3 / / method
// [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
    
    
    [[NSRunLoop currentRunLoop] run];
        // Test whether RunLoop is enabled. If RunLoop is enabled, you cannot come here because RunLoop is enabled.
        NSLog(@" RunLoop not enabled"); } Let's also write the 'touchesBegan' method in our new thread - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // Use performSelector to call run2 in self.thread
    [self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:NO];
}

- (void) run2 {
    NSLog(@"----run2------");
}
Copy the code

We have to make sure that the thread doesn’t die before we can accept the time processing in the background, so if we don’t implement adding an NSPort or an NSTimer, we’ll find that after the run method, the thread will die, and subsequent execution of the TouchBegan method won’t work.

Once you have implemented one of the above three methods, you can see that the run method is finished, and then click on the screen. You can continue to execute the test method, because the thread self.thread is always in the background, waiting for events to join it, and then execute.