Q&A

1. What is Runloop?

Run Loop is a mechanism that allows threads to handle events at any time without exiting. A RunLoop is actually an object that manages the events and messages it needs to process and provides an entry function to execute the logic of the Event Loop. Once the thread executes the function, it will remain in the “receive message -> wait -> process” loop inside the function until the loop ends (such as the incoming quit message) and the function returns. Let the thread sleep when no message is being processed to avoid resource usage and wake up as soon as a message arrives.

2. How does Runloop relate to threads?

  • There is one Runloop for each thread.

  • The main thread has Runloop by default.

  • Runloops for child threads are created as lazy loads.

  • Runloops are stored in a global mutable dictionary where threads are keys and runloops are values.

3. Running mode of RunLoop

There are five RunLoop modes. RunLoop only runs in one mode. To switch modes, pause the current mode and restart another mode

-kcfrunloopDefaultMode, the default running mode of the App, usually the main thread is run in this mode. -uitrackingrunloopMode, tracking user interaction events (used for ScrollView tracking touch sliding, Ensure the interface slip is not affected by other Mode) - kCFRunLoopCommonModes, pseudo Mode, is not a real running Mode - UIInitializationRunLoopMode: When just start the App the first to enter the first Mode, start will no longer use - GSEventReceiveRunLoopMode: after the completion of acceptance within the system, is usually not usedCopy the code

4. Internal runloop logic?

  • RunLoop is actually just such a function, with a do-while loop inside. When you call CFRunLoopRun(), the thread stays in the loop forever; This function does not return until it times out or is stopped manually.

  • Internal logic:

    • If it is a Timer event, process the Timer and restart the loop, skipping to step 2

    • If the input source is fired, process the event (deliver the event on documentation)

    • If RunLoop is woken up manually but has not timed out, restart the loop and skip to step 2

    • Events arrive at port-based input sources (Source0)

    • Timer Indicates that the Timer is executed

    • External manual wake up

    • Time out for RunLoop

  1. Notify the Observer that RunLoop has been entered

  2. Notify the Observer that a Timer is about to be processed

  3. Notify the Observer that non-port-based input sources are about to be processed (Source0 is about to be processed)

  4. Work with non-port-based input sources that are ready (work with Source0)

  5. If the port-based input source is ready and waiting for processing, handle the event immediately. Go to Step 9 (handling Source1)

  6. Notify the Observer that the thread is about to sleep

  7. Put the thread to sleep until one of the following events occurs

  8. Notify the Observer that the thread has just been awakened (has not yet processed the event)

  9. Process pending events

5. When is autoreleasePool released?

  • 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.

  • The code that executes on the main thread is usually written inside such callbacks as event callbacks and Timer callbacks. These callbacks are surrounded by AutoreleasePool created by RunLoop, so there is no memory leak and the developer does not have to show that the Pool was created.

6.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.

7. 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.

8. How does PerformSelector work?

  • 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.
  • 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 this method will fail.

9. PerformSelector: afterDelay: this method in the child thread work?

  • It doesn’t work. The child thread has no Runloop by default and therefore no Timer. This can be done using GCD’s dispatch_after

10. Event response process?

  • Apple registered a Source1 (based on the Mach port) to the receiving system, the callback function as __IOHIDEventSystemClientQueueCallback ().

  • When a hardware event (touch/lock/shake, etc.) occurs, an IOHIDEvent event is first generated by IOKit. Framework and received by SpringBoard. Details of this process can be found here. SpringBoard only receives events such as buttons (lock screen/mute etc.), touch, acceleration and proximity sensor, and then forwards them to the App process using Mach port. Then apple registered the Source1 will trigger the callback, and call the _UIApplicationHandleEventQueue () distribution within the application.

  • _UIApplicationHandleEventQueue () will wrap IOHIDEvent process, and as a UIEvent processing or distribution, including identifying UIGesture/processing screen rotation/send UIWindow, etc. Usually click event such as a UIButton, touchesBegin/Move/End/Cancel events are completed in the callback.

11. Gesture recognition process?

  • When _UIApplicationHandleEventQueue () to identify a gesture, the first call will Cancel the current touchesBegin/Move/End series callback to interrupt. The system then marks the corresponding UIGestureRecognizer as pending.

  • Apple registered a Observer monitoring BeforeWaiting (Loop entering hibernation) events, the callback function Observer is _UIGestureRecognizerUpdateObserver (), Internally it gets all GestureRecognizer that has just been marked for processing and executes the GestureRecognizer callback.

  • This callback is handled when there are changes to the UIGestureRecognizer (create/destroy/state change).

11. Which is more accurate, CADispalyTimer or Timer?

More accurate CADisplayLink

  • 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.

  • NSTimer is less precise. For example, when NSTimer is triggered, if runloop is blocked, the trigger time will be delayed to the next runloop cycle. And NSTimer has added a tolerance property that allows users to set a tolerance range of triggering time delays.

  • 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.