Project code
runloopDemo
CFCoreFoundation source
directory
- From the main thread
runloop
When to turn it on runloop
How are objects storedrunloop
How did you get started? How did you get outRunloop do-while
What did- Listening to the
Runloop
The state of the - Resident threads and how to destroy resident threads
runloop
andperformSelector
- The network requests the main thread callback to achieve synchronization
- Runloop optimizes scrolling pit points in TableView
- Runloop stuck monitoring
runloop
andautoreleasepool
- Interface to update
From the main threadrunloop
When 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
runloop
How 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:
runloop
There is a one-to-one correspondence between objects and threadsrunloop
The object is stored in a global dictionary for this global fieldkey
Is a thread object,value
isrunloop
object
runloop
How 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 ginseng
stopAfterHandle
For YES, then the processing is donesource
Will quitrunloop
- Its timeout is up
- Called externally
CFRunloop
stop - be
_CFRunLoopStopMode
stop
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-while
What 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 createdrunloop
The 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 therunloop
And use it toCFRunLoopStop
Try exiting runloop
[self.myRunloop run]; Replace [self myRunloop runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]].
Successful exitrunloop
And threadrun
After 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
runloop
andperformSelector
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
runloop
andautoreleasepool
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__CFRunLoopRun
Analysis of the