Project code

runloopDemo

CFCoreFoundation source

directory

  • From the main threadrunloopWhen to turn it on
  • runloopHow are objects stored
  • runloopHow did you get started? How did you get out
  • Runloop do-whileWhat did
  • Listening to theRunloopThe state of the
  • Resident threads and how to destroy resident threads
  • runloopandperformSelector
  • The network requests the main thread callback to achieve synchronization
  • Runloop optimizes scrolling pit points in TableView
  • Runloop stuck monitoring
  • runloopandautoreleasepool
  • Interface to update

From the main threadrunloopWhen to turn it on

You go in UIApplicationMain in your app’s main, and you loop through it all the time, NSLog(@” can I go? “); It will not be called

Why does main return an int? I can’t return since it’s all dead loop

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

Copy the code

After entering UIApplicationMain, will then call application: didFinishLaunchingWithOptions: method, in this way is open runloop, through monitoring runloop state, Set a breakpoint on the runloop callback and look at the stack


runloopHow are objects stored

CFRunLoopGetCurrent: CFRunLoopGetCurrent: CFRunLoopGetCurrent: CFRunLoopGetCurrent: CFRunLoopGetCurrent

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

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

Thread_cfrunloopget0 = thread (); thread (); thread (); thread ()

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {// If the parameter is null, the main thread is used by defaultif(pthread_equal(t, kNilPthreadT)) { t = pthread_main_thread_np(); } __CFLock(&loopsLock); // static CFMutableDictionaryRef __CFRunLoops = NULL; // Check if the Runloop dictionary exists. If it does not, create a Runloop dictionary and add the main thread Runloop to itif(! __CFRunLoops) { __CFUnlock(&loopsLock); 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); __CFLock(&loopsLock); Runloop CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFUnlock(&loopsLock); // If not, create a Runloop and add it to the dictionaryif(! loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));if(! loop) { CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __CFUnlock(&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

The general logic of the code is

Create a global dictionary and add runloop to the main thread. 3. If there is no loop, create a loop and add it to the global dictionary // pseudocodeif(! Loops () {loop (pthread_t) {loop (pthread_t) {loop (pthread_t)if(! Loop) {1. Create loop 2. Add to dictionary}return loop
Copy the code

So:

  • runloopThere is a one-to-one correspondence between objects and threads
  • runloopThe object is stored in a global dictionary for this global fieldkeyIs a thread object,valueisrunloopobject

runloopHow did you get started? How did you get out

What does a runloop do when it runs around

First runloop has six state changes

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), KCFRunLoopBeforeTimers = (1UL << 1), // Timer kCFRunLoopBeforeSources = (1UL << 2), // Source kCFRunLoopBeforeWaiting = (1UL << 5), kCFRunLoopAfterWaiting = (1UL << 6), KCFRunLoopExit = (1UL << 7), // About to exit Loop};Copy the code

So, when runloop is started, it listens for input sources (ports port, source0, source1), timers, processes events if any, and sleeps if none

But this is not the case. Instead, the runloop is repeatedly entered (using the run method to start runloop).

The CFRunLoopRun function is a do-while operation. Each time the CFRunLoopRunSpecific is executed, even if the runloop is iterated over, CFRunLoopRun does a do-while, so it enters the runloop again


void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do{result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10,false);
        CHECK_FOR_FORK();
    } while(kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); }Copy the code

CFRunLoopRunSpecific makes some pre-determinations, such as determining that the current Mode is null and returning directly. This also indicates that the runloop must have an input source or timer *** before starting the runloop

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ ... // Prejudgment, such as' Mode 'is null, direct'return`
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        Boolean did = false;
        if (currentMode) __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopUnlock(rl);
        returndid ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished; }... // The callback is about to enter runloopif(currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); Runloop result = __CFRunLoopRun(rl, currentMode, seconds,returnAfterSourceHandled, previousMode); // Exit runloopif(currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); .return result;
}
Copy the code

Now look at the __CFRunLoopRun function

// Simplify the code, Static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef RLM, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {do{/ / listeningsourceThe timer,if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        if(rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); / / processingsource0
       Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); // About to go to sleepif(! poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); . // Exit the runloop conditionif (sourceHandledThisLoop &&stopAfterHandle) {// Finish processingsourceaftersourceHandledThisLoop will be YES // stopAfterHandle, if CFRunloop is called, Is to NO / / can look back on the CFRunLoopRun function / / retVal = kCFRunLoopRunHandledSource; }else if(timeout_context->termTSR < mach_absolute_time()) {retVal = kCFRunLoopRunTimedOut; }else if(__CFRunLoopIsStopped(rl)) {CFRunloop stopped __CFRunLoopUnsetStopped(rl); retVal = kCFRunLoopRunStopped; }else if(RLM ->_stopped) {// Stopped by _CFRunLoopStopMode RLM ->_stopped =false;
            retVal = kCFRunLoopRunStopped;
        } else if(__CFRunLoopModeIsEmpty(rl, RLM, previousMode)) {retVal = kCFRunLoopRunFinished; }}while(0 = retVal);
}

Copy the code

There are four conditions for exiting runloop

  • The ginsengstopAfterHandleFor YES, then the processing is donesourceWill quitrunloop
  • Its timeout is up
  • Called externallyCFRunloopstop
  • be_CFRunLoopStopModestop

CFRunLoopRun If stopAfterHandle is set to NO, the runloop is started using the run method and the source is processed without exiting the runloop

If CFRunLoopRunInMode is used, you can specify whether to exit the runloop after processing the source


Runloop do-whileWhat did

During the do-while, we do the following

  • Listen to source (source1 is port based thread communication (touch/lock/shake etc.), source0 is not port based, including: UIEvent, performSelector), listen to the process
  • Listen for timer events and handle them
  • When there is no source and timer, sleep is not listening, or keep listening, only when there is an event, wake up, continue processing

When we trigger an event (touch/lock screen/shake, etc.), IOKit. Framework generates an IOHIDEvent event. IOKit is a hardware driven framework of Apple. IOHIDServices and IOHIDDisplays are two components. IOHIDServices deals with user interactions by encapsulating events into IOHIDEvents objects, which are then forwarded to the required App processes using Mach ports. Then Source1 will receive IOHIDEvent, then the callback __IOHIDEventSystemClientQueueCallback (), __IOHIDEventSystemClientQueueCallback trigger Source0 within (), Source0 trigger _UIApplicationHandleEventQueue again (). So the touch event is seen inside Source0.

Summary: Touch events are sent through a Mach port, encapsulated as source1, and then converted to source0

1. One runloop corresponds to one thread, multiple modes, and one mode corresponds to multiple sources, observers, and timers

struct __CFRunLoop { pthread_t _pthread; CFMutableSetRef _commonModes; // CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; . / / simplified}; struct __CFRunLoopMode { CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; CFMutableArrayRef _timers; . / / simplified};Copy the code

2. There are five common modes

  • KCFRunLoopDefaultMode: The default Mode of the App, in which the main thread is normally run.
  • UITrackingRunLoopMode: interface tracing Mode, used by ScrollView to track touch sliding, ensuring that the interface sliding is not affected by other modes.
  • UIInitializationRunLoopMode: in the first to enter the first Mode when just start the App, start after the completion of the will no longer be used.
  • GSEventReceiveRunLoopMode: accept system internal Mode of events, usually in less than.
  • Kcfrunloopcommonmode: This is a placeholder Mode and has no effect.

In addition to the above 5 modes, there are other modes, but rarely encountered here

4. The child thread does not automatically start the runloop. Before starting the runloop manually, it must have an input source and timer (the input source is to listen to the port and can get different events). If mode is null or modeItem is null, return directly

Listen for the status of Runloop

Runloop has six states

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), KCFRunLoopBeforeTimers = (1UL << 1), // Timer kCFRunLoopBeforeSources = (1UL << 2), // Source kCFRunLoopBeforeWaiting = (1UL << 5), kCFRunLoopAfterWaiting = (1UL << 6), KCFRunLoopExit = (1UL << 7), // About to exit Loop};Copy the code

You can listen for these six states through this code

CF_EXPORT CFRunLoopObserverRef CFRunLoopObserverCreate(CFAllocatorRef allocator, CFOptionFlags activities, Boolean repeats, CFIndex order, CFRunLoopObserverCallBack callout, CFRunLoopObserverContext *context);
Copy the code

Where, the parameters are respectively

CFRunLoopObserverCreate parameters

1. Don’t understand

2. Listen for runloop status changes

3. Check whether wiretapping is repeated

4. If you don’t understand, pass 0

5. Callback function pointer (need to write your own function)

6. CFRunLoopObserverContext object

Defining function Pointers

static void runLoopOserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, Void *info) {//void *info is what we want to pass to OC, this can be converted to OC object, we passed to self RunloopObserver *target = (__bridge RunloopObserver) *)(info); //void *info is the self(ViewController) we passed earlierif(target.callback) { target.callback(observer, activity); }}Copy the code

Define the CFRunLoopObserverContext object, which is used for communication

Typef struct {CFIndex version; Void * info; Const void *(*retain)(const void *info); // reference void (*release)(const void *info); CFStringRef (*copyDescription)(const void *info); } CFRunLoopObserverContext;Copy the code

Create a listener

// Create a listener static CFRunLoopObserverRef Observer; // CFRunLoopObserverCreate parameter. 1. Do not understand 2. Listen for runloop status changes 3. Do not understand, 6. 0 5. The transfer function pointer object CFRunLoopObserverContext observer = CFRunLoopObserverCreate (NULL, kCFRunLoopAllActivities, YES, 0, &runLoopOserverCallBack, &context); // Register to listen on CFRunLoopAddObserver(runLoopRef, Observer, kCFRunLoopCommonModes); / / destroyed CFRelease (observer);Copy the code

Resident threads and how to destroy resident threads

So let’s do performSelector and the child thread’s perform… AfterDelay and perform.. Onthreads need to be executed on the thread with runloop enabled

This is done by adding a non-repeating timer to the runloop

- (void)test1
{
    [self.myThread setName:@"StopRunloopThread"]; self.myRunloop = [NSRunLoop currentRunLoop]; / / performSelector: afterDelay: the principle of which is added to runloop don't repeat the timer [self performSelector: @ the selector (performSelAferDelayClick) withObject:nil afterDelay:1]; [self.myRunloop run]; NSLog(@"Will I go?");
}
Copy the code

Perform (perform) Perform runloop (perform) ¶ Perform runloop (perform) ¶

Retrieving a runloop calls the CFRunLoopRunSpecific function (cfrunloop.c).

Return kCFRunLoopRunFinished when currentMode is empty (no input source or timer)

if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
	Boolean did = false;
	if (currentMode) __CFRunLoopModeUnlock(currentMode);
	__CFRunLoopUnlock(rl);
	return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
Copy the code

The following code implements a resident thread

The idea is to add a port to the current thread’s runloop and have it listen for that port. Because you can always listen for that port, the runloop will not exit

To keep the runloop from exiting, you need to have an input source or a repeating timer to listen to

- (void)test2
{
    [self.myThread setName:@"StopRunloopThread"];
    self.myRunloop = [NSRunLoop currentRunLoop];
    self.myPort = [NSMachPort port];
    
    [self.myRunloop addPort:self.myPort forMode:NSDefaultRunLoopMode]; [self.myRunloop run]; // Since run, the thread has been doing itdo-while operation // equivalent to the above code isdo-while, then the following code does not take NSLog(@)"Will I go?");
}
Copy the code

When a thread is started, a corresponding thread is createdrunloopThe object?

No, call the method that gets the current runloop, internal implementation: if the current runloop does not exist, create one, return the current runloop does exist

So this is self.myRunloop = [NSRunLoop currentRunLoop]; Generates a runloop corresponding to the current thread


How do I destroy resident threads?

1. To destroy the resident thread, you must first exit the runloop.

Exit runloop when there is no input source or timer to listen to

If we call [NSThread exit]; , the thread is destroyed, but the code in the thread is still not executed, such as NSLog(@” Can I walk? “); .

The runloop does not exit, which can cause some problems, such as the following code

- (void)test2
{
    [self.myThread setName:@"TestThread"]; self.myRunloop = [NSRunLoop currentRunLoop]; self.myPort = [NSMachPort port]; // Add a port to listen on NSMachPort (this port is the input source, because it can always listen on this port, so the runloop does not exit, it will always dodo- while) [self myRunloop addPort: self, myPortforMode:NSDefaultRunLoopMode]; [self.myRunloop run]; // [self.myRunloop run]; Causes the following code to fail because runloop is one of themdo-while loop,do-while listening on source, processing source [self.testptr release]; }Copy the code

Because runloop does not exit, [self.testptr release]; TestPtr cannot be freed if it is not executed

2. How do I exit runloop

Resident if threads are implemented through the listener port, then call [self. MyRunloop removePort: self, myPort forMode: NSDefaultRunLoopMode]; Remove the port and you can destroy it

Destruction may not be successful at this point, because the system may be listening to some other source

If you want to implement resident threads by adding repeat timers (this is not advisable because it is more inefficient than adding listening ports and requires the runloop to be woken up again and again)

- (void)test11
{
    [self.myThread setName:@"TestThread"]; self.myRunloop = [NSRunLoop currentRunLoop]; self.myPort = [NSMachPort port]; // Before starting runloop, you need to have an input source or timer // timer (if adding timer, do not repeat, So listen to once dropped out) NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval: 1 repeats: YES block: ^ (NSTimer * _Nonnull timer) { NSLog(@"The timer implementation");
    }];
    
    [self.myRunloop addTimer:timer forMode:NSDefaultRunLoopMode]; [self.myRunloop run]; // [self.myRunloop run]; Causes the following code to fail because runloop is one of themdo-while loop,do-while listens to the source and processes the source NSLog(@"Will I go?");
    
}
Copy the code

If NSTimer repeats is NO, then a timer event is executed and the runloop is exited

It is not feasible to exit the runloop by removing the port, ending the timer, or removing the known input source or timer, because the system may add some input sources to the runloop of the current thread

3. Run CFRunLoopStop to exit the Runloop

- (void)test3
{
    [self.myThread setName:@"TestThread"]; self.myRunloop = [NSRunLoop currentRunLoop]; self.myPort = [NSMachPort port]; // Add a port to listen on NSMachPort (this port is the input source, because it can always listen on this port, so the runloop does not exit, it will always dodo- while) [self myRunloop addPort: self, myPortforMode:NSDefaultRunLoopMode]; [self performSelector:@selector(runloopStop) withObject:nil afterDelay:1]; [self.myRunloop run]; // [self.myRunloop run]; Causes the following code to fail because runloop is one of themdo-while loop,do-while listens to the source and processes the source NSLog(@"Will I go?");
}

- (void)runloopStop
{
    NSLog(@"Executive stop");
    CFRunLoopStop(self.myRunloop.getCFRunLoop);
}
Copy the code

Output:

2020-05-03 20:10:12.614130+0800 Runloop[60465:2827474] 2020-05-03 20:10:12.614465+0800 Runloop[60465:2827474] 2020-05-03 20:10:12.615214+0800 Runloop[60465:2827474] 2020-05-03 20:10:12.615634+0800 Runloop[60465:2827474] is going to sleep, 2020-05-03 20:10:13.615638+0800 Runloop[60465:2827474] just woke up from hibernation, 2020-05-03 20:10:13.616005+0800 Runloop[60465:2827474] Run stop 2020-05-03 20:10:13.616194+0800 Runloop[60465:2827474] 2020-05-03 20:10:13.616360+0800 Runloop[60465:2827474] 2020-05-03 20:10:13.616511+0800 Runloop[60465:2827474] 2020-05-03 20:10:13.616648+0800 Runloop[60465:2827474] 2020-05-03 20:10:13.616765+0800 Runloop[60465:2827474] is going to sleep,Copy the code

I did exit runloop, but I immediately entered it again

The reason is:

There are three ways to start a thread

Runloop - (void)run; Runloop - (void)runUntilDate:(NSDate *)limitThe Date; / / processingsource- (void)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate; // CFRunloop void CFRunLoopRun(void) SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, BooleanreturnAfterSourceHandled) //  returnAfterSourceHandled as NO SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, BooleanreturnAfterSourceHandled) //  returnAfterSourceHandled to YESCopy the code

Run and runUntilDate: both repetitions runMode:beforeDate:

See how NSRunLoop exits

So after stop, we exit runloop, but since we started runMode with run, we repeatedly call runMode:beforeDate: starts again

3. UserunMode:beforeDate:Start therunloopAnd use it toCFRunLoopStopTry exiting runloop

[self.myRunloop run]; Replace [self myRunloop runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]].

Successful exitrunloopAnd threadrunAfter the runloop exits, the thread is destroyed. If the thread has no work to do, the thread will be destroyed.

2020-05-03 20:21:30.330067+0800 Runloop[60593:2834891] 2020-05-03 20:21:30.330303+0800 Runloop[60593:2834891] About to process Timer, 2020-05-03 20:21:30.330639+0800 Runloop[60593:2834891] 2020-05-03 20:21:30.330906+0800 Runloop[60593:2834891] is going to sleep, 2020-05-03 20:21:31.330956+0800 Runloop[60593:2834891] just woke up from sleep, 2020-05-03 20:21:31.331329+0800 Runloop[60593:2834891] run stop 2020-05-03 20:21:31.331591+0800 Runloop[60593:2834891] 2020-05-03 20:21:31.331783+0800 Runloop[60593:2834891Copy the code

While using the self. MyRunloop runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]] can exit the runloop success, but still have a problem, When runloop finishes processing the source, it exits the runloop and does not want to re-enter the runloop as it would if it had called the run method

So it still doesn’t work that way

Finally, the best way is either to exit the runloop manually or to exit the runloop without finishing processing the source

BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while(shouldKeepRunning) {// runMode returns a value, when runloop is started, it doesn't return, so it doesn't keep calling this method, runloop exits, Will run up [theRL runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]}Copy the code

When you want to exit runloop, shouldKeepRunning should be set to NO


runloopandperformSelector

PerformSelector: withObject: afterDelay: principle, add a not repeat the runloop timer

The child thread calls this method only when runloop is enabled

See the performSelector: onThread: withObject: waitUntilDone:

MyThread = [[PermanentThread alloc] initWithTarget:self selector:@selector(myThreadStart) object:nil]; [self.myThread start]; NSLog(@"1");
[self performSelector:@selector(performWait) onThread:self.myThread withObject:nil waitUntilDone:NO];
NSLog(@"2");

- (void)performWait
{
    NSLog(@"3");
}

Copy the code

If waitUntilDone is NO, then you do not wait for sel execution to complete before proceeding down

The output is 1, 2, 3

If it is YES, the current thread will be stuck and wait for SEL execution to finish

The output is 1, 3, 2


The network requests the main thread callback to achieve synchronization

Requirement Description:

You’re given an interface that is a network request, and the callback is coming back from the main thread, and now you’re required to call this interface and wait for the callback to come back before the rest of the code can proceed

- (void)netRequestComplete:(void(^)(void))complete { Dispatch_after (dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{ dispatch_async(dispatch_get_main_queue(), ^{if(complete) { complete(); }}); }); }Copy the code

Using a semaphore causes a deadlock

dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self netRequestComplete:^{
    NSLog(@"3"); // Deadlock dispatch_semaphore_signal(sema); }]; Dispatch_time (DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC))); NSLog(@"2");   
Copy the code

The correct way to use CFRunloopRun

[self netRequestComplete:^{
    NSLog(@"3"); CFRunLoopStop([NSRunLoop currentRunLoop].getcfrunloop); CFRunLoopStop([NSRunLoop currentRunLoop].getcfrunloop);  }]; // CFRunLoopRun() is quite largedo-while CFRunLoopRun() cannot be executed; NSLog(@"2");
    
Copy the code

Runloop optimizes scrolling pit points in TableView

This point is learned from this article UITableView Performance Optimization – Intermediate

I did an experiment

First of all, perform does allow you to slide tableView when scrolling without loading images to achieve optimization

But it turns out from this experiment that when I stop scrolling, all of the previous indexPath triggers the logIndexRow: method

If the image is loaded at this time, then it is unnecessary, because the cell is delimited, there is no need to load

Perform adds an input source to the runloop’s defaultmode, but when the scroll ends, perform adds an input source to the runloop’s defaultmode, Switch back to defaultMode and these input sources will be fired

// This selector can be a method of loadImg [self performSelector:@selector()logIndexRow:)
               withObject:indexPath
               afterDelay:0
                  inModes:@[NSDefaultRunLoopMode]];
Copy the code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell  *cell = [tableView dequeueReusableCellWithIdentifier:@"123"];
    
    if(! cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"123"]; } // when sliding, no call is madelogIndexRow:, because mode is sliding, but perform is also the input source, these events will be accumulated in NSDefaultRunLoopMode, when the switch to NSDefaultRunLoopMode, It will execute these input source events [self performSelector:@selector(logIndexRow:)
               withObject:indexPath
               afterDelay:0
                  inModes:@[NSDefaultRunLoopMode]];
    
    cell.textLabel.text = @"123";
    cell.textLabel.textColor = [UIColor redColor];
    
    return cell;
}
Copy the code

Runloop stuck monitoring

To quote from the source code __CFRunLoopRun analysis

From kCFRunLoopBeforeSources as a starting point to kCFRunLoopBeforeWaiting before sleep, a lot of work is done ———— execute block, deal with source0, update interface… Once this is done, the RunLoop sleeps until the RunLoop is awakened by timer, source, libdispatch, and sends a kCFRunLoopAfterWaiting notification. We know that the refresh rate of the screen is 60fps, that is, 1/60s ≈ 16ms. If a RunLoop exceeds this time, the UI thread may be stuck. BeforeSources to AfterWaiting can be roughly considered as the start and end of a RunLoop. As for other states, such as BeforeWaiting, it may sleep after updating the interface. At this point, the APP is already inactive and is unlikely to lag. KCFRunLoopExit, on the other hand, is triggered after the RunLoop exits. The main RunLoop is unlikely to exit voluntarily except by changing mode, and this cannot be used as a stall check.

*** is about to process source*** until *** finishes sleeping ***. If this process takes longer than one frame, frame loss may occur (frame loss causes stutter).

So why is this process likely to stall if it takes a frame of time?

We must first understand the principle of screen display, is probably CPU decoding the text, layout, drawing, picture, and then submit the bitmap to the GPU, the GPU to render, rendering is finished, according to the V – sync signal, update the buffer, at the same time, the pointer to the video controller, can also according to V – sync signal to buffer cache read a frame, Display it on the screen

In other words, rendering from CPU ->GPU should be completed within 16ms to ensure that it can be read by the video controller within the specified time. Otherwise, the video controller will read the last frame, which leads to lag

So between processing source and ending sleep, if the CPU is working on a task that goes beyond 16ms, it may not be able to render a frame in 16ms

runloopandautoreleasepool

The App starts, apple registered in the main thread RunLoop two Observer, the callback is _wrapRunLoopWithAutoreleasePoolHandler (). The first Observer monitors an event called Entry(about to enter Loop), which creates an automatic release pool within its callback by calling _objc_autoreleasePoolPush(). Its order is -2147483647, the highest priority, ensuring that the release pool is created before all other callbacks. The second Observer monitors two events: calling _objc_autoreleasePoolPop() and _objc_autoreleasePoolPush() while waiting (ready to sleep) torelease the old pool and create a new one; _objc_autoreleasePoolPop() is called upon Exit(about to Exit the Loop) torelease the automatic release pool. The order of this Observer is 2147483647, the lowest priority, ensuring that its release pool occurs after all other callbacks.

Set breakpoints _wrapRunLoopWithAutoreleasePoolHandler symbols, can from the assembly code, see autoreleasepush, pop

Interface to update

I don’t know how to prove…


Refer to the article

Exit method of NSRunLoop

Runloop by Daming

NSURLConnection execution process

Lots of runloop problems and examples

In-depth understanding of RunLoop by YYModel

The source code__CFRunLoopRunAnalysis of the