What is RunLoop?

A Runloop is an object that manages events and messages by maintaining an internal event loop. Yes, it’s an object. If you’ve ever used main in C, you know that when main is done, the program ends and exits. But why can iOS apps continue to run after the main function runs? This is where Runloop comes in. This is the most basic use of Runloop.

Runloop is an object. How do I get it?

  • Foundation [NSRunloop currentRunLoop]; Get the current thread’s RunLoop object [NSRunLoop mainRunLoop]; Get the Runloop object for the main thread
  • Core Foundation CFRunLoopGetCurrent(); Get the RunLoop object CFRunLoopGetMain() for the current thread; Get the Runloop object for the main thread

The purpose of Runloop:

A Runloop is essentially an event processing loop: a loop that continuously dispatches work and processes input events;

Runloop loops work when there is work, no work sleep; In effect, it guarantees that the thread will not be terminated until it is finished;

Without it, the main thread completes its startup task and then terminates.

Runloop is designed to reduce unnecessary CPU idling.

Runloop usage scenarios:

This is used when you need to interact with threads.

What is the implementation mechanism of RunLoop?

To make it easier to understand the Runloop mechanism, let’s write some pseudocode to represent the Runloop.

function runloop() { initialize(); do { var message = get_next_message(); Process_message (message); } while (message! = quit); // When the quit condition is triggered, Runloop exits}Copy the code

As you can see from the code, Runloop is processed in an “receive message -> wait -> process” loop until the loop ends (such as an incoming quit message).

What is the core of RunLoop?

Is how it sleeps when there is no message processing and wakes up when there is. RunLoop is not a simple while loop. Sleep is not a sleep. Sleep is also CPU intensive. So how does it achieve true hibernation?

When there are no messages to be processed, it switches from user mode to kernel mode. When the user mode enters kernel mode, the current thread controller is handed over to kernel mode. The dormant thread is suspended and no longer consumes CPU resources.

Note the concepts of user-state and kernel-state, as well as the mach_msg() method. Kernel-state this mechanism relies on the system kernel (Mach in Darwin, the core component of the Apple operating system).

RunLoop is an object that manages events/messages through an internally maintained time loop

  • Hibernation avoids resource usage when there are no messages to process
    • User mode -> kernel mode
  • Wake up as soon as you get a message
    • Kernel mode -> User mode

How can I wake up Runloop

Source:

That is, events that wake up the Runloop; For example, when the user clicks on the screen, an input source is created; Source0:

Non-system events; Contains only one callback (function pointer) and cannot actively fire events; To use this command, call CFRunLoopSourceSignal(source) first. Mark this Source as pending; CFRunLoopWakeUp(runloop) is then manually called to wake up the runloop to handle the event;

Source1:

System events: contains a mach_port and a callback (function pointer) that is used to send messages to each other through the kernel and other threads; This source actively wakes up the Runloop thread;

Timer:

Tasks registered for execution using the NSTimer API fall into this category;

Observer:

An Observer can listen for runloop state changes and react to them;

Why is only the main thread Runloop turned on automatically?

UIApplicationMain is called in the mian() function, which creates a main thread for UI processing. To keep the program running and receiving events, open a runloop in the main thread to keep the main thread permanent.

Since UIApplicationMain is called in main, the RunLoop is started in UIApplicationMain.

PerformSelector: afterDelay: this method is in the child thread work? Why is that? How to solve it?

Does not work, child threads have no Runloop by default. When calling NSObject performSelecter: afterDelay: after its internal will actually create a Timer and add to the current thread RunLoop. So if the current thread does not have a RunLoop, this method will fail. AfterDelay requirements like this can be implemented using GCD’s dispatch_after.

When the performSelector: onThread: when, in fact, it will create a Timer is added to the corresponding thread, in the same way, if the corresponding thread no RunLoop will also fail, the method.

UITableViewCell has a UILabel that shows the stopwatch time implemented by NSTimer. Does the label refresh when I scroll the Cell of the TableView? Why is that?

I don’t refresh. When the NSTimer object is added to the main running loop with NSDefaultRunLoopMode, the Mode of TableView (ScrollView) will be switched during the scrolling process, resulting in the NSTimer will not be scheduled any more. When we scroll, we want to not schedule, so we should use the default mode. If you want the timer to run while scrolling, you should use Common Mode. This is implemented by CFRunloopAddTimer(runloop,timer,commonMode). Synchronize the event source timer with the same mode.

Why only refresh the UI on the main thread

All of the UI that we’re using is coming from this base library CALLED UIKit. Because objC is not a thread-safe language, there is the problem of multi-threaded read/write synchronization. Using locking is very expensive for the operating system, consuming a lot of system resources (memory, time slice rotation, CPU processing speed…). In addition, the above mentioned system events are received and processed in the main thread, if the UI asynchronous thread will also have the problem of synchronous processing events, so multi-touch gestures and other events should be kept in the same thread relative to the UI is the optimal solution.

On the other hand, the screen renders at 60 frames (60Hz/ SEC), which is called back 60 times per second (iPad Pro is 120Hz/ SEC), and our Runloop will ideally call back 60 times per clock (iPad Pro 120 times). The high frequency of the call is so that the screen image display can be vertically synchronized without lag. In asynchronous threads it is difficult to ensure that this process is updated synchronously. Even if you can guarantee it, the overhead of the system relative to the main thread, thread scheduling and so on is going to take up most of the resources and it’s not worth it to just do one thing on the same thread.

PerformSelector and Runloop

When NSObect’s performSelector: correlation is called, a timer timer is created internally and added to the current thread’s runloop. If the current thread has not started the runloop, the method will not be called.

One of the most common problems in development is this performSelector, which leads to delayed release of the object, and one of the things to notice in development is that you can use one-pass NSTimer instead.

How do I keep threads alive?

Add while(true){} to the NSThread’s method to simulate the operation of runloop. Combine the GCD semaphore to process the task in the {} code block.

But make sure you start runloop correctly

Void memoryTest {for (int I = 0; i < 100000; ++i) { NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [thread start]; [self performSelector:@selector(stopThread) onThread:thread withObject:nil waitUntilDone:YES]; } // Thread stop - (void)stopThread {CFRunLoopStop(CFRunLoopGetCurrent()); NSThread *thread = [NSThread currentThread]; [thread cancel]; } - (void)run {@autoreleasepool {NSLog(@"current thread = %@", [NSThread currentThread]); NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; if (! self.emptyPort) { self.emptyPort = [NSMachPort port]; } [runLoop addPort:self.emptyPort forMode:NSDefaultRunLoopMode]; [runLoop runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]]; }} // (void)printSomething {NSLog(@"current thread = %@", [NSThread currentThread]); [self performSelector:@selector(printSomething) withObject:nil afterDelay:1]; } - (void)stopButtonDidClicked (id)sender {[self performSelector:@selector(stopRunloop) onThread:self.thread withObject:nil waitUntilDone:YES]; } - (void)stopRunloop { CFRunLoopStop(CFRunLoopGetCurrent()); }Copy the code

How to use Runloop in AFNetworking?

The AFURLConnectionOperation class is built on NSURLConnection and is expected to receive Delegate callbacks in the background thread. AFNetworking creates a separate thread for this purpose and starts a RunLoop in this thread:

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}

+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });
    return _networkRequestThread;
}
Copy the code

Before RunLoop starts, there must be at least one Timer/Observer/Source, so AFNetworking creates a new NSMachPort and adds it to it before [RunLoop run]. Normally, the caller needs to hold this NSMachPort (mach_port) and send messages inside the loop through this port from the external thread; But port is added here only to keep the RunLoop from exiting, not to actually send messages.

- (void)start { [self.lock lock]; if ([self isCancelled]) { [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread]  withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } else if ([self isReady]) { self.state = AFOperationExecutingState; [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } [self.lock unlock]; }Copy the code

When need the background threads execute tasks, AFNetworking by calling [NSObject performSelector: onThread:..] Throw the task into the RunLoop of the background thread.

Explain the role of Runloop in NSTimer

NSTimer is essentially CFRunLoopTimerRef, and you can interchangeably use these two classes. When an NSTimer registers with a RunLoop, the RunLoop registers events for its repeated time points. Such as 10:00, 10:10, 10:20. In order to save resources, RunLoop does not call back to Timer on time when a block occurs. A point in time has been missed and will not be executed for you after the extension. Like waiting for a bus, if there’s a bus at 10:10 and I miss it, I have to wait for the 10:20 bus. The 10:10 is not coming back.

How does Runloop relate to threads?

Runloop and are one-to-one, one thread for each Runloop. The main thread has Runloop by default. As you can see from the data structure, threads are created without runloops by default. You need to manually create runloops for threads.

With threads, why do you have runloops?

The primary function of Runloop is how it sleeps when there is no message processing and wakes up when there is. This improves CPU resource efficiency. Another function of runloop is message processing. You can’t do that with threads alone.

GCD in Runloop?

GCD is returned from the child thread to the main thread, and RunLoop is only triggered in this case. The Source 1 event of the RunLoop is raised.

Which is more accurate, CADispalyTimer or Timer

CADisplayLink is more accurate, of course.

IOS devices refresh their screens at a fixed rate, and CADisplayLink is normally called at the end of each refresh with a high degree of accuracy.

If the Runloop is blocked, the trigger time of the NSTimer will be delayed until the next Runloop cycle. So the timing of NSTimer is very unreliable.

CADisplayLink can be used for more specific purposes, such as constant redrawing of UI, such as custom animation engines or rendering of video playback. NSTimer can be used for a much wider range of tasks that require single or cyclic timing. The advantage of CADisplayLink over NSTimer for UI-related animation or display content is that we don’t need to worry too much about the screen refresh rate, because it is already synchronized with the screen refresh rate.

What is the relationship between Runloop and thread?

Each thread has a unique RunLoop object corresponding to it;

The RunLoop for the main thread is created automatically, and the RunLoop for the child thread needs to be created actively.

The RunLoop is created on the first fetch and destroyed at the end of the thread

Runloops are stored in a global Dictionary, with threads as keys and runloops as values

What is the mode of Runloop?

Specifying the priority of events in the run loop, threads need different modes to run, to respond to different events, to handle different situational patterns. (For example, when you can optimize tableView, you can set UITrackingRunLoopMode to not perform some operations, such as setting pictures, etc.)

RunLoop has a one-to-one relationship with threads and a one-to-many relationship with mode. Mode and Source/Timer/Observer are also one-to-many

To the + scheduledTimerWithTimeInterval: way to trigger the timer, when sliding on the page list, the timer will suspend the callback, why?

When you swipe scrollView, the RunLoop of the main thread will switch to the Mode UITrackingRunLoopMode, which also performs tasks under UITrackingRunLoopMode. However, timer is added under NSDefaultRunLoopMode, so the timer task will not be executed. Only when the task of UITrackingRunLoopMode is executed and runloop switches to NSDefaultRunLoopMode, Before the timer continues.

How to solve the problem that timer will pause the callback when sliding a list on a page?

Put the Timer in NSRunLoopCommonModes and do it

How do I add events to both modes?

NSRunLoopCommonModes are not an actual mode, but a technical solution for synchronizing Source/Timer/Observer modes into multiple modes.

How does runloop respond to user actions?

  • Source1 captures user touch events
  • Source0 to handle touch time

Talk about several states of runLoop

KCFRunLoopEntry = (1UL << 0), // About to enter Loop kCFRunLoopBeforeTimers = (1UL << 1), KCFRunLoopBeforeSources = (1UL << 2), Source kCFRunLoopBeforeWaiting = (1UL << 5), KCFRunLoopAfterWaiting = (1UL << 6), kCFRunLoopExit = (1UL << 7), // About to exit Loop kCFRunLoopAllActivities = 0x0FFFFFFFU // All status changes

How do you make sure that the child thread data comes back to update the UI without breaking the user’s slide?

Encapsulate an event and add the event to the defaultMode of the main thread.

How to implement a resident thread?

  1. Open a RunLoop for the current thread.

  2. Add an event loop such as Port/Source to the RunLoop to maintain the RunLoop.

  3. Start the RunLoop

How does AutoreleasePool relate to Runloop?

After the iOS application is launched, two observers are registered to manage and maintain AutoreleasePool.

Application has just started the default registered a lot of the Observer, there are two of the Observer is the callout _wrapRunLoopWithAutoreleasePoolHandler, these two are two listening and automatic release pool related;

· The first Observer listens for RunLoop entry and calls objc_autoreleasePoolPush() to add a sentinel object flag to the current AutoreleasePoolPage to create the automatic release pool. The order of this Observer is -2147483647, the highest priority, to ensure that all callbacks occur before;

· The second Observer listens for RunLoop sleep and RunLoop exit. When going to sleep, objc_autoreleasePoolPop() and objc_autoreleasePoolPush() are called to clean up from the newly added object until the sentry object is encountered. When exiting RunLoop, objc_autoreleasePoolPop() is called torelease objects in the pool automatically. The order of this Observer is 2147483647, which has the lowest priority and is guaranteed to occur after all callbacks;