RunLoop que

The relationship between Runloop and thread is one-to-one. A thread can only execute one task at a time, and the thread will be destroyed after the task is completed. The purpose of Runloop is to manage and schedule the thread so that it will not be destroyed when there is no task.

For the main thread, the runloop is created by default as soon as the program starts. For child threads, runloops are lazily loaded and only created when we use them

Default NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode(Core Foundation) Event Tracking NSEventTrackingRunLoopMode (Cocoa) Common modes NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes(Core Foundation)

Connection NSConnectionReplyMode (Cocoa)

Modal NSModalPanelRunLoopMode (Cocoa)

Five RunLoop

  1. CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION(call timer, performSelector)

  2. CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE(GCD main queue)

  3. FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK Block (response)

  4. CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION(observer)

  5. CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION

  6. CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION

RunLoop in relation to threads

The mode of the main thread runloop is Default, Event tracking is when the touch Event is generated, and Common modes are placeholders equivalent to NSDefaultRunLoopMode + UITrackingRunLoopMode. Some timer touches fail because they may be added to the default Runloop instead of common modes. The child thread must also create a Runloop to keep the thread alive if the task is not destroyed

RunLoop is a message processing mechanism that is assigned by the system to process various kinds of information. A simple program performs the process of run -> process the calculation -> complete -> end the task

The function annotation for UIApplication Main is to use the value of nsPrincipalClass in info.plist if nil is specified for PrincipalClassName. If the nsPrincipalClass key is not specified, the uiApplication class is used. The delegate class will be instantiated using init. So it’s in the AppDelegate. An App startup process

int main(int argc, char * argv[]) {
    @autoreleasepool {

        // If nil is specified for principalClassName, the value for NSPrincipalClass from the Info.plist is used. If there is no
        // NSPrincipalClass key specified, the UIApplication class is used. The delegate class will be instantiated using init.
        returnUIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));; }}Copy the code

#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()
#endif
CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    if(! __main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS neededreturn __main;
}
Copy the code

The current main thread pthread_main_thread_NP is passed in as the main thread entry point _CFRunLoopGet0b in the macro definition

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
   /// 'Check whether this is the main thread'
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    if(! __CFRunLoops) { __CFSpinUnlock(&loopsLock); ///'Here we define CFMutableDictionaryRef to create the main Runloop and associate it with it'CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); 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); } / / /'Let's take the t pointer out and get the runloop and we can see that there's a one-to-one relationship between thread and runloop. Key -- value'
    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; } // T release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __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); }}return loop;
}
Copy the code

If the Runloop is not enabled, the timer will not be executed. If the Runloop is not enabled, the timer will not be executed. If the Runloop is not enabled, the timer will not be executed

RunLoop structure

Runloop is a structure and it’s an object

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;          /* locked for accessing mode list */
    __CFPort _wakeUpPort;           // used for CFRunLoopWakeUp 
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes; '/// Models collection '
    CFMutableSetRef _commonModeItems; '/// item is a set '
    CFRunLoopModeRef _currentMode;  '/ / / the current models'
    CFMutableSetRef _modes; '/// Models collection '
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};
Copy the code

Model

RunLoop Model structure

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    CFMutableSetRef _sources0;' /// 集合'
    CFMutableSetRef _sources1; 'collection / / /'
    CFMutableArrayRef _observers; '/ / / array'
    CFMutableArrayRef _timers; '/ / / array'
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    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

RunLoop has a one-to-many relationship with model, and it exists as a set in RunLoop. Runloop can only run under one model. But you can have multiple models.

timer

The timer is stored in the Runloop model as an array and one of the key methods in the source code is CFRunLoopRun which calls this method and it ends up going to

CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) a **__CFRunLoopDoBlocks** passes this method

static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked
    if(! rl->_blocks_head)return false;
    if(! rlm || ! rlm->_name)return false;
    Boolean did = false;
    struct _block_item *head = rl->_blocks_head;
    struct _block_item *tail = rl->_blocks_tail;
    rl->_blocks_head = NULL;
    rl->_blocks_tail = NULL;
    CFSetRef commonModes = rl->_commonModes;
    CFStringRef curMode = rlm->_name;
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);
    struct _block_item *prev = NULL;
    struct _block_item *item = head; "The items in the Model exist as a linked list, and as long as the items exist they remain until they are empty."
    while (item) {
        struct _block_item *curr = item;
        item = item->_next; "Access the lower level of the list and assign a value to it,"
        Boolean doit = false;
        if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
            "// If the timer adds modes equal to those of our current runloop // or curr->_mode = kCFRunLoopCommonModes equal // the transaction will execute A timer can be executed whenever it's added to the current model or commanModel."
            doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        } else {
            doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        }
        if(! doit) prev = curr;"Execute block of response if doit exists"
        if (doit) { 
            if (prev) prev->_next = item;
            if (curr == head) head = item;
            if (curr == tail) tail = prev;
            void (^block)(void) = curr->_block;
            CFRelease(curr->_mode);
            free(curr);
            if (doit) {
                __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);"Block model runloop." "
                did = true;
            }
            Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc
        }
    }
    __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm);
    if (head) {
        tail->_next = rl->_blocks_head;
        rl->_blocks_head = head;
        if(! rl->_blocks_tail) rl->_blocks_tail = tail; }return did;
}
Copy the code

It’s __CFRunLoopDoBlocks because there’s going to be a Runloop on the outer layer of a timer so let’s see

RunLoop.current.addtimer()
Copy the code

The underlying implementation of

static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
    CFStringRef modeName = (CFStringRef)value;
    CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
    CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
    if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
        CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
    } else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
        CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
    } else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {"That's why the TimerID that corresponds to the item takes this method."CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName); }}"You can see that the Timer mode is added to the CFRunLoopAddTimer method."
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if(! __CFIsValid(rlt) || (NULL ! = rlt->_runLoop && rlt->_runLoop ! = rl))return;
    __CFRunLoopLock(rl);
    if (modeName == kCFRunLoopCommonModes) { "If model were commanmodes"
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        "Set the model of the timer object to CommonModels, and then add the timer to items."
        CFSetAddValue(rl->_commonModeItems, rlt); 
        if(NULL ! =set) {
            CFTypeRef context[2] = {rl, rlt};
            /* add new item to all common-modes */
            // timer -- items()
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            CFRelease(set); }}else {
        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
        if(NULL ! = rlm) {if(NULL == rlm->_timers) { CFArrayCallBacks cb = kCFTypeArrayCallBacks; cb.equal = NULL; rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb); }}if(NULL ! = rlm && ! CFSetContainsValue(rlt->_rlModes, rlm->_name)) { __CFRunLoopTimerLock(rlt);if (NULL == rlt->_runLoop) {
                rlt->_runLoop = rl;
            } else if(rl ! = rlt->_runLoop) { __CFRunLoopTimerUnlock(rlt); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl);return;
            }
            CFSetAddValue(rlt->_rlModes, rlm->_name);
            __CFRunLoopTimerUnlock(rlt);
            __CFRunLoopTimerFireTSRLock();
            __CFRepositionTimerInMode(rlm, rlt, false);
            __CFRunLoopTimerFireTSRUnlock();
            if(! _CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) { // Normally we don't do this on behalf of clients, but for // backwards compatibility due to the change in timer handling... if (rl ! = CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl); } } if (NULL ! = rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl); }Copy the code

This method is called by adding a timer object to items, and then consuming (calling) __CFRunLoopDoBlocks, getting the corresponding item, checking with doIT, and creating a heap block that refers to a timer. The call to block is then made.


Observer

The Observer listens for RunLoop’s callback status and notifies RunLoop whenever RunLoop’s Model changes

Source

Source0

Contains function callback pointer signal to handle wajeup wake RunLoop to handle events, handle intra-app events, things the app itself is responsible for managing

CFRunLoopSourceContext context = { 0, NULL, NULL, NULL, NULL, NULL, NULL, schedule, cancel, perform, }; /** Argument one: Pass NULL or kCFAllocatorDefault to use the current default allocator. Parameter two: Priority index indicating the order in which run loop sources are processed. Here I pass 0 for the autonomous callback argument 3: the structure that holds context information for the running loop source */ CFRunLoopSourceRefsource0 = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
    CFRunLoopRef rlp = CFRunLoopGetCurrent();
    // source--> runloop specifies modesourceCFRunLoopAddSource(RLP,source0, kCFRunLoopDefaultMode); CFRunLoopSourceSignal(source0); CFRunLoopWakeUp(RLP); // Cancel removing CFRunLoopRemoveSource(RLP,source0, kCFRunLoopDefaultMode);
    CFRelease(rlp);
Copy the code

Source1

Match_port and function callback Pointers are primarily used for communication between threads

@property (nonatomic, strong) NSPort* subThreadPort;
@property (nonatomic, strong) NSPort* mainThreadPort;

- (void)setupPort{
    self.mainThreadPort = [NSPort port];
    self.mainThreadPort.delegate = self;
    // port - source1 -- runloop
    [[NSRunLoop currentRunLoop] addPort:self.mainThreadPort forMode:NSDefaultRunLoopMode]; [self task]; } - (void) task { NSThread *thread = [[NSThread alloc] initWithBlock:^{ self.subThreadPort = [NSPort port];  self.subThreadPort.delegate = self; [[NSRunLoop currentRunLoop] addPort:self.subThreadPortforMode:NSDefaultRunLoopMode]; 
        [[NSRunLoop currentRunLoop] run]; "/// I need to add Run because the child thread Runloop is not enabled by default"}]; [thread start]; // main thread -- dispatch_async(dispatch_get_global_queue(0, 0), ^{NSLog(@)"% @", [NSThread currentThread]); // 3

        NSString *str;
        dispatch_async(dispatch_get_main_queue(), ^{
            // 1
            NSLog(@"% @", [NSThread currentThread]); }); }); // Mach - (void)handlePortMessage:(id)message {NSLog(@)"% @", [NSThread currentThread]); // 3 1

    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([message class], &count);
    for (int i = 0; i<count; i++) {
        
        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
//        NSLog(@"% @",name);
    }
    
    sleep(1);
    if(! [[NSThread currentThread] isMainThread]) { NSMutableArray* components = [NSMutableArray array]; NSData* data = [@"woard" dataUsingEncoding:NSUTF8StringEncoding];
        [components addObject:data];
        "The main thread sends a message to the child thread"
        [self.mainThreadPort sendBeforeDate:[NSDate date] components:components from:self.subThreadPort reserved:0];
    }
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    NSMutableArray* components = [NSMutableArray array];
    NSData* data = [@"hello" dataUsingEncoding:NSUTF8StringEncoding];
    [components addObject:data];
    ///"Child thread sends message to main thread"
    [self.subThreadPort sendBeforeDate:[NSDate date] components:components from:self.mainThreadPort reserved:0];
}
Copy the code

The next article will analyze the RunLoop source code to see how they are implemented

RunLoop summary

The purpose of Runloop is to manage and schedule threads so that they are not destroyed when there is no work to do. A Runloop has a one-to-one relationship with threads. A thread can only execute one task at a time, and the thread is destroyed after the task is completed. For the main thread, runloop creates the child thread as soon as the program starts. Runloop is disabled by default. The relation between Runloop and thread is one-to-one and the relation between mode is a pair of N, which means that multiple models can be held at the same time. In the three models, the items of burden sharing, Source, Tiemr and Observer are also one-to-many, and multiple items can be held. The Timer Observer exists as an array, while the Source exists as a collection in the Runloop. Observers are astute about handling app transactions, handling touch events, etc., and other things. Source0 is about NSPort, which is used for thread communication Runloop works like this:

  1. Notify the Observer that Runloop is about to enter
  2. Notify the Observer that the Timer will be processed
  3. Notify the Observer that Source0 will be processed
  4. Processing Source0
  5. If there is Source1, go to step 9
  6. Notify the Observer that the thread is about to go to sleep
  7. Dormant waiting to wake up (source0, Timer, external manual wake up)
  8. Notify the Observer that the thread has just been awakened
  9. Process Source1 and return to step 2
  10. Notify the Observer that Runloop is coming