Finish reading Runloop

Keywords: iOS network socket REST

Runloop is a basic component closely related to threads, and it is behind many functions related to threads. This article will summarize runloop from the following aspects:

  • What is runloop
  • The role of runloop
  • The relationship between runloop and thread
  • Detailed introduction of runloop and source code analysis
  • Analysis of runloop principle
  • runloop application

What is runloop

runloop Apple official document address

  • Runloop is still a thing as its name suggests. To put it bluntly, it's a kind of cycle, but it's more advanced. The general do..while loop will cause the CPU to enter the busy waiting state, while the runloop is a kind of "idle" waiting.

runloopOfrunThe source code of the method is as follows,It is ado..whileloop

void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
  • When there is no event, runloop will enter the sleep state. When there is an event, runloop will go to the corresponding Handler to handle the event. Runloop allows threads to be busy when they need to do something, and to sleep when they don't need to.
  • runloop is actually an object that provides an entry function.

The role of runloop

  • Keep the program running continuously and avoid thread destruction
  • Handle various events of APP (touch, timer, performSelector)
  • Save cpu resources, performance of the provider (do as you go, rest as you go)

The use of runloop in the system

In iOS system, runloop is used for the following, and the method name of the call can be seen by viewing the stack through the breakpoint:

  • block application: cfrunoop? Is? Calling? Out? To? A? block
  • Call timer: cfrunoop? Is? Calling? Out? To? A? Timer? Callback? Function
  • Response source0: cfrunoop? Is? Calling? Out? To? A? Source0? Performance? Function
  • Response source1: cfrunoop? Is? Calling? Out? To? A? Source1? Performance? Function
  • GCD main queue: cfrunoop ﹣ is ﹣ serving ﹣ the main ﹣ dispatch ﹣ queue
  • observer source: cfrunoop? Is? Calling? Out? To? An? observer? Callback? Function

Breakpoint view runloop information

Add a breakpoint in timer's block, and then uncheck the button indicated by the left arrow (selected by default). You can see the call information of runloop, cfrunloop, is calling out to a timer, callback function, and the source code is as follows:

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
}

The runloop method names of other calls summarized above can be checked in the above debugging mode.

The relationship between runloop and thread

  1. runloop and thread are one-to-one correspondence
  2. runloop is created when it is first acquired by a thread and destroyed when the thread ends
  3. The main thread starts runloop by default, and the sub thread starts manually (when the program starts, start the main thread runloop, [NSRunLoop currentRunLoop] run])

The figure shows the role of Runloop in threads: receiving events from input source and timer source, and then processing events in threads.

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;
}

CFRunLoopGet0() is called in the source code. Here is the main thread pthread main thread np(). It is defined as the main thread as follows

#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()

There is also a method to get the current thread runloop: CFRunLoopGet0 is also called, but the current thread pthread [self() is passed in.

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

Next, let's look at the source code of the function ﹐ cfrunloopget0 (including the main thread and the sub thread) to get the thread runloop.

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
    //Get runloop from 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 RunLoop of the main thread
    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);
    }
    //Look for 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);
        }
    }
  • If the currently stored dictionary container does not exist, a container CFMutableDictionaryRef variable dictionary is created first
  • The second step uses the main thread to create a main thread runloopcfrunloopref mainloop = \cfrunloopcreate (pthread_main_thread_np());
  • Step 3 CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); save the main thread and its runloop in the CFMutableDictionaryRef dictionary container in the form of key value
  • As mentioned above, when you first come in, whether it's getMainRunloop or get sub thread's runloop, the main thread's runloop will always be created.
  • See cfrunloopref loop = (cfrunloopref) cfdictionarygetvalue (_cfrunloops, pthreadpointer (T)); you can use threads to take out the runloop saved in the dictionary.
  • If runloop is not found in the dictionary, a new runloop object is created based on the current sub thread and saved in the dictionary.
  • The last step is if (pthread [u equal (T, pthread [self())) {...} to determine whether the current thread is the thread passed in. If so, create a callback. If the thread is destroyed, destroy the current runloop.
  • Here we verify the above conclusion 1 and 2: runloop and thread are one-to-one correspondence (Dictionary saving). Runloop is created when it is first acquired by a thread (and whether it is the main thread runloop or the sub thread runloop, it will always create the main thread's runloop), and is destroyed at the end of the thread (destroyed by callback).

runloop code validation

At the interruption point of AppDelegate, you can see that the main thread calls the CFRunloopRun method, so it proves the above conclusion three: the main thread turns on the runloop by default [image-ad314f-1571322546197]) test runloop code is 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];

DLThread.mIt only has the following code

-(void)dealloc{
    NSLog(@"Thread destroyed");
}

Run the above code and find that timer is not printed, indicating that timer is not successfully opened in the sub thread. Then add the code to run the runloop of the current thread, 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];

It is found that timer has been printing, and two conclusions are proved here: timer operation is related to runloop, and the runloop of sub thread needs to be opened manually.

So how to stop timer? A new tag value isstop is added 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){
                [NSThread exit];
            }
        }];
        [[NSRunLoop currentRunLoop] run];
    }];
    thread.name = @"Test";
    [thread start];

It is found that after the thread is destroyed, timer stops. This proves the above conclusion: runloop is destroyed at the end of the thread.

runloop source code analysis

Click to download runloop source code: password 3kww

What we need 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;            // The used for CFRunLoopWakeUp kernel sends a message to the port to wake up runloop
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop 
    pthread_t _pthread;  //Thread corresponding to RunLoop
    uint32_t _winthread; //
    CFMutableSetRef _commonModes; //It stores the string and records all mode s marked as common.
    CFMutableSetRef _commonModeItems;//item(source, timer, observer) storing all commonMode
    CFRunLoopModeRef _currentMode; //Current running mode
    CFMutableSetRef _modes;   //CFRunLoopModeRef is stored
    struct _block_item *_blocks_head; //Used in doblocks
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};

As you can see, in fact, runloop is a structure object, which contains a thread, a currently running mode, N modes, N common modes.

  • One to one correspondence between runloop and thread
  • runloop contains multiple modes, and mode contains multiple mode items (sources, timers, observers)
  • runloop can only run under 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 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? And the value is CFRunLoopSourceRef.
    __CFPortSet _portSet;    //Save all ports to be monitored, 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 */
};

A CFRunLoopMode object has a name, N source0, N source1, timer, observer and port. Visible events are managed by Mode, while RunLoop manages Mode.

The relationship between them is as follows:

Mode is allowed to be customized, but at least one mode item (source/timer/observer) should be included. The same mode item can be held by multiple modes

Apple's three open runloop modes:

  • Nsdefaultrunloopmode (kcfrnloopdefaultmode): the default state, in which app usually runs
  • Uitrackingronloop mode: interface tracking mode (for example, it is not affected by other modes when scrollview is sliding)
  • Nsrnloop commonmodes (kcfrnloop commonmodes): the collection of the first two modes. You can add a custom mode to the collection with the CFRunLoopAddCommonMode function.

There are two other mode s, just to understand:

  • GSEventReceiveRunLoopMode: receiving system internal mode, usually not used
  • UIInitializationRunLoopMode: private, only used when app starts, not in collection after use

CFRunLoopSource

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits; ////Used to mark the Signaled status. source0 will be processed only when it is marked as Signaled
    pthread_mutex_t _lock;
    CFIndex _order;            /* immutable */
    CFMutableBagRef _runLoops;
    union {
    CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;    /* immutable, except invalidation */
    } _context;
};

CFRunloopSourceRef is the data Source abstract class object (protocol) of runloop. From the Source code, you can see the common body (union: store different data types in the same memory location). It can be seen that the Source can be divided into two types:

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;

source0: handle internal events of app, and manage (trigger) the app itself. For example: UIEvent CFSocket. Break point will basically see it.

  • source0 is not Port based. Contains only one callback (function pointer), which does not actively trigger events.
  • CFRunLoopSourceSignal (source) marks this event as pending
  • CFRunLoopWakeUp to wake up runloop and let it handle events

Implementation steps of custom source:

  1. Create an underlying source0 sourcecfrunloopsourceref source0 = cfrunloopsourcecreate (cfallocatorgetdefault(), 0, & Context);
  2. Add our created source0 to runloop cfrunloopaddsource (RLP, source0, kcfrrunloopdefaultmode)
  3. Execution signal, mark to be processed CFRunLoopSourceSignal
  4. Wake up runloop to handle CFRunLoopWakeUp
  5. Remove source CFRunLoopRemoveSource
  6. Release 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;

source1:

  • It is managed by runloop and Mach port and driven by Mach port. It contains a Mach port and a callback (function pointer), which are used to send messages to each other through the kernel and other threads.
  • It can actively wake up runloop (managed by the operating system kernel, such as CFMachPort,CFMessagePort)
  • It is also allowed to implement its own Source, but generally it will not

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 */
};

As an observer, it can monitor the status change of runloop, report the status change of runloop to the outside, and trigger many mechanisms in the framework (such as CAAnimation).

In the CFRunloop.h file, you can see the status of observer monitoring as follows:

/* 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
};

It corresponds to the observer in the runloop process as shown in the figure 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 */
};
  • CFRunLoopTimer is a timer that can throw a callback at a set time point
  • CFRunLoopTimer and NSTimer are toll free bridged, which can be converted to each other.
  • CFRunLoopTimer can be packaged in three ways: 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;

These three kinds of timer s are briefly summarized as follows:

Analysis of runloop principle

void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, 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);
}

CFRunLoopRun and CFRunLoopRunInMode both 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);

    ///First, find the corresponding mode according to the modeName
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);

    ///Notify observers that runloop is about to enter the loop.
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

    ///Internal function, enter loop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

    ///Notify observers that runloop is about to exit.
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    return result;
}

The above source code is simplified, but the actual source code is more complex. According to the source code, we can draw the following conclusions:

  • Notify the observer before entering the run loop. The status is kcfrnloop entry.
  • Notify observer after exiting run loop with status of kcfrnloopexit
  • CFRunLoopRun function called when entering runloop

__Cfrunlooprun (core focus)

///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 event is about to be processed
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

        ///Notify Observers that the Source event is about to be processed
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)

        ///Handling Blocks
        __CFRunLoopDoBlocks(rl, rlm);

        ///Process sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);

        ///Process sources0 and return to YES
        if (sourceHandledThisLoop) {
            ///Handling Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }

        ///Judge whether there is port message (Source1)
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            ///Processing messages
            goto handle_msg;
        }

        ///Notify Observers: going to sleep
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);

        ///Waiting 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: wake up, end sleep
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

    handle_msg:
        if (cover Timer awaken) {
            ///Processing Timers
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        } else if (cover GCD awaken) {
            /// processing gcd
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else if (cover Source1 awaken) {
            ///Wake up from Source1, 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;
}

The above is the source code of runloop core function (relatively clear and easy to understand) Click to download runloop source code: password 3kww There is also a function called CFRunLoopServiceMachPort that listens for wake-up port messages. The system kernel suspends this thread, stays in the status of Mach MSG trap, and waits for the message of Mach port (the port used for wake-up). The thread will go into sleep until it is waked up by another thread or a thread of another process sending a Mach MSG message to the port

__CFRunLoopServiceMachPort

/**
 *  Receive messages for the specified kernel port
 *
 *  @param port        Port to receive messages
 *  @param buffer      Message buffer
 *  @param buffer_size Message buffer size
 *  @param livePort    For now, it is understood as the active port. The value is MSG - > msgh_local_port when the message is received successfully, and the value is mach_port_nullwhen the message is timed out.
 *  @param timeout     Timeout, in ms. if timeout occurs, RunLoop will go to sleep.
 *
 *  @return Return true when the message is received successfully other cases return false
 */
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; //Flag bit of message 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 the Mach MSG are pointers.
        //If the message body is sent or received directly, memory replication will occur frequently and performance will be lost.
        //XNU uses a single kernel to solve this problem. All kernel components share the same address space, so only the pointer of the message needs to be passed.
        ret = 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, assign msgh? Local? Port to livePort
        if (MACH_MSG_SUCCESS == ret) {
            *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
            return true;
        }
         //MACH_RCV_TIMEOUT
        //If no message is received after timeout, the system returns Mach ﹣ RCV ﹣ timed ﹣ out.
        //In this case, release the buffer, and assign the 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, the oversized message is put in the queue and an error is returned to Mach ﹣ RCV ﹣ to ﹣ large.
        //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;
}

runloop application

Event response

  • When a hardware event (touch / lock screen / shake / accelerate) occurs, IOKit.framework first generates an IOHIDEvent event and it is accepted by SpringBoard, and then it is forwarded by the MAC port to the required App process.
  • Apple registered a Source1 to accept system events, triggered Source0 through a callback function (so Event is actually based on Source0), and called uuiapplicationhandleeventqueue() for internal application distribution. _UIApplicationHandleEventQueue() will process and package IOHIDEvent as UIEvent for processing or distribution, including identifying UIGesture / processing screen rotation / sending to UIWindow, etc.

gesture recognition

  • When the UIApplicationHandleEventQueue() above recognizes a gesture, it first calls Cancel to interrupt the current touchesBegin/Move/End series of callbacks. The system then marks the corresponding UIGestureRecognizer as pending.
  • Apple has registered an Observer to monitor the beforewaiting event. The callback function of the Observer is "uigesturerecognizer updateobserver(), which will get all the gesturerecognizers just marked for processing and execute the callback of GestureRecognizer.
  • When there is a change (create / destroy / state change) of UIGestureRecognizer, this callback will process accordingly.

Interface refresh

  • When the UI changes (Frame changes, UIView/CALayer structure changes), or the setNeedsLayout/setNeedsDisplay method of UIView/CALayer is called manually, the UIView/CALayer is marked as pending.
  • Apple has registered an Observer to monitor BeforeWaiting and Exit. In its callback function, it will traverse all the UIView/CALayer to be processed to perform the actual drawing and adjustment, and update the UI interface.

AutoreleasePool

  • The main thread Runloop registers two Observers, whose callbacks are "wrap Runloop with autorelease poolhandler".
  • Observers1 listens for Entry events: the highest priority is to ensure that a release pool is created before all callbacks, and an automatic release pool is created by calling objc autoreleasepoolpush() in the callback.
  • Observers2 listens for BeforeWaiting and Exit events: the lowest priority ensures that the release pool is released after all callbacks. BeforeWaiting event: call objc autoreleasepoolpop() and objc autoreleasepoolpush() to release the old pool and create a new pool, Exit event: call objc autoreleasepoolpop() to release the auto release pool

tableView delays loading pictures to ensure fluency

For the method of loading image to ImageView, use PerformSelector to set the running mode of the current thread's RunLoop, kcvronloop defaultmode, so that the method of loading image will not be executed when sliding [self.imgView performSelector:@selector(setImage:) withObject:cellImg afterDelay:0 inModes:@[NSDefaultRunLoopMode]];

Timer is not affected by ScrollView's sliding

  • +Timerwithtimerinterval... Create timer
  • [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes] add timer to the current runloop and use occupation mode
  • runloop run/runUntilData manually start the sub thread runloop
  • Use GCD to create timers. Timers created by GCD will not be affected by RunLoop
// Get queue
    dispatch_queue_t queue = dispatch_get_main_queue();

    // Create a timer (dispatch source or OC object in essence)
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

    // Set various properties of timer (when to start the task and how often to execute it)
    // Time parameter of GCD, generally nanosecond (1 second = = 9 power nanosecond of 10)
    // Start execution 1 second later than the current time
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));

    //Every second
    uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
    dispatch_source_set_timer(self.timer, start, interval, 0);

    // Set callback
    dispatch_source_set_event_handler(self.timer, ^{
        NSLog(@"------------%@", [NSThread currentThread]);

    });

    // Start timer
    dispatch_resume(self.timer);

GCD

  • Dispatch [async (dispatch] get [main] queue) uses RunLoop
  • libDispatch sends a message to the main thread's Runloop to wake it up, and obtains the block from the message, and executes the block in the callback "cfrunloop" is "serving" the main thread "dispatch" queue "

NSURLConnection

  • When using NSURLConnection, you will pass in a Delegate. When [connection start] is called, the Delegate will receive the event callback continuously.
  • The internal part of the start function will get CurrentRunLoop, and then add 4 source0s (sources that need to be triggered manually) to DefaultMode. CFMultiplexerSource is responsible for various Delegate callbacks, and CFHTTPCookieStorage handles various cookies.
  • When we start the network transmission, we can see that asurlconnection creates two new threads: com.apple.asurlconnectionloader and com.apple.CFSocket.private. The CFSocket thread processes the underlying socket connection. The thread of NSURLConnectionLoader will use RunLoop to receive the events of the underlying socket and notify the upper Delegate through the previously added Source0.

AFNetworking

  • Using runloop to start resident thread
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    [runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [runloop run];
  • Add [NSMachPort port](source1) to runloop so that runloop does not exit. In fact, no message is sent to this port.

AsyncDisplayKit

Following the pattern of QuartzCore/UIKit framework, a similar interface update mechanism is implemented: an Observer is added to the main thread's RunLoop, and the kcfrnloop before waiting and kcfrnloop exit events are monitored. When receiving the callback, all the tasks previously put into the queue are traversed and executed one by one.

Caton detection

  • Dispatch [semaphore] is a semaphore mechanism. When a semaphore arrives or times out, it will continue to go down. Otherwise, wait. If it times out, the result returned must not be 0, and the semaphore arrives at 0. GCD semaphore dispatch
  • Detect the stuck by monitoring the status of mainRunloop and the characteristics of semaphore blocking threads, and record the stack information when the interval between kcfrnloop beforesource and kcfrnloop beforewaiting exceeds the custom threshold.
  • Recommended articles: RunLoop actual combat: real-time Caton monitoring

FPS detection

  • When creating the CADisplayLink object, a selector will be specified, and the CADisplayLink object created will be added to the runloop, so a method can be called with the frequency of screen refresh.
  • Calculate the number of execution times in the called method. Divide the number of times by time to calculate the FPS.
  • Note: iOS normal refresh rate 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];
}

Anti crash handling

  • Nssetuncaultexceptionhandler (& handleexception); listen for exception signals SIGILL,SIGTRAP,SIGABRT,SIGBUS,SIGSEGV, SIGFPE
  • Create a Runloop in the callback method, and run all the runmodes of the main thread as an alternative to the main Runloop of the application.
CFRunLoopRef runloop = CFRunLoopGetCurrent();
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);
    }
}
  • It can record stack information, upload server or pop up friendly prompt page.

Resident thread

You can add the thread you created to the Runloop and do some frequently processed tasks, such as detecting the network status, regularly uploading some information, etc.

- (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 sentence, you will find that runloop will hang when it is created, because if runloop does not have CFRunLoopSourceRef event source input or timer, it will die immediately.
      The following method adds an NSport to runloop, that is, add an event source, add a timer, or observer, so that runloop will not hang up*/
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];

    // Methods 1, 2 and 3 have the same effect. Let runloop run indefinitely
    [[NSRunLoop currentRunLoop] run];
   }

    // Method 2
    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

    // Method 3
    [[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];
}

All of the above are personal data collection and partial understanding of runloop. If there is any error, please correct it. Welcome to discuss.

Welcome to join iOS development exchange learning group (password 123), we learn and grow together

Included: Original address

Posted by korngold on Fri, 18 Oct 2019 15:20:08 -0700