say something

Due to the recent busy project, the preparation is very hasty, and my personal level is limited, let’s throw out the brick to attract jade. There is a wrong understanding of the place, but also ask bigwigs to correct. 🤦 🤦 🤦

intro

Why does the program keep running?

Take a look at the APP entry function:
Int main(int argc, char * argv[]) {NSString * appDelegateClassName; @autoreleasepool { // Setup code that might create autoreleased objects goes here. appDelegateClassName = NSStringFromClass([AppDelegate class]); }return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Copy the code

The main function () returns a value of type int. UIApplicationMain() is called inside main() and returns its return value; UIApplicationMain() is an entry function that starts a RunLoop. This default RunLoop is associated with the main thread, causing the main thread to stay. So UIApplicationMain() never returns, ensuring that main() never returns, keeping the program running.

What is Runloop? Let’s start with the Event Loop

Tips: Swift doesn’t have main?

Official explanation:

In Xcode, Mac templates default to include a “main.swift” file, but for iOS apps the default for new iOS project templates is to add @UIApplicationMain to a regular Swift file. This Causes the compiler to synthesize a mainentry point for your iOS app, and eliminates the need for a “main.swift” file.

Import UIKit @uiApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate {}Copy the code

Event Loop

Typically, a thread can execute only one task at a time, and when it is finished, the thread exits. If we want threads not to exit and to be able to handle events at any time, the usual logic goes like this:

func loop() {
    initialize();
    do {
        var message = get_next_message();
        process_message(message);
    } while(message ! = quit); }Copy the code

This model is often referred to as the Event Loop. The key is how to make the thread sleep when there is no event processing to save performance and respond to events as soon as they arrive.

Event Loop has been implemented in many systems and frameworks. In macOS/iOS, Runloop has been implemented.

RunLoop profile

Brief introduction:

RunLoop literally means to run a loop, which means to do something in a loop while the program is running. Inside it is a do-while loop, in which you schedule your sleep and sleep properly, do things when you have something to do, and rest when you have nothing to do.

Basic functions:

  1. Keep the program running continuously;
  2. Handle various events in the App (such as touch events, timer events, Selector events, GCD back to the main thread, etc.);
  3. Reduce CPU idling, save CPU resources, improve program performance.
  4. Management autoreleasepools

Note:

  1. RunLoop can select only one Mode to start, i.eCurrentMode;
  2. If you need to switch Mode, you can only exit Loop and specify another Mode to enter.
  3. ifCurrentModeIf there is no Source or Timer, exit RunLoop directly.

Application Examples:

  1. Child thread survival (enable runloop for child thread and ensure mode is not empty)
  2. Fixed sliding page timer stop issue (add timer to CommonModes)
  3. To perform certain tasks in a particular mode (performSelector: withObject: afterDelay: inModes)
  4. Trap monitoring (listen for runloop, BeforeSources -> BeforeWaiting and AfterWaiting -> BeforeTimers interval)
  5. (Listen for RunLoop, separate multiple time-consuming operations, and execute one time-consuming task each time RunLoop wakes up.)
  6. And so on..

andAutoreleasePoolThe relationship between:

Runloop manages the creation and release of AutoreleasePool: first creation: last destruction when Runloop starts; creation and destruction at other times when Runloop exits: When runloop is about to go to sleep, all events are processed, the previous release pool is destroyed, and a new one is created.

App starts, the apple in the main thread RunLoop registered two Observer, the callback is _wrapRunLoopWithAutoreleasePoolHandler ().

  1. The first Observer monitors the following events:
  • Entry(about to enter Loop), which is called within the callback_objc_autoreleasePoolPush()Create an automatic release pool. The order is – 2147483647 (0 x7fffffff),Highest priorityTo ensure that the release pool is created before any other callbacks.
  1. The second Observer monitors two events:
  • BeforeWaiting(ready to go to sleep)_objc_autoreleasePoolPop()_objc_autoreleasePoolPush()Release the old pool and create a new pool;
  • Called when Exit(about to Exit Loop)_objc_autoreleasePoolPop()To release the automatic release pool. The order of this Observer is 2147483647(0x7FFFFFFF),Lowest priorityTo ensure 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 are no memory leaks and we don’t have to explicitly create pools.

andthreadThe relationship between:

  1. In the first timeTo obtainIs created and destroyed at the end of the thread.
  2. Threads and runloops correspond one to one. Runloops are stored internally by dictionaries, with threads as keys and runloops as values.
  3. Runloops for the main thread are created automatically when the application starts, while runloops for child threads need to be created and started manually.

GCDBack to the main thread

When dispatch_async(dispatch_get_main_queue(), block) is called, libDispatch sends a message to the main thread’s RunLoop, the RunLoop wakes up and takes the block from the message, This block is executed in the __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() callback. But this logic is limited to dispatches to the main thread, dispatches to other threads are still handled by libDispatch.

Incident response

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. SpringBoard only receives events for buttons (lock screen/mute, etc.), touch, acceleration, proximity sensors, etc., and then forwards them to the required App process using a 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, distributed to the UIWindow. As a UIButton click, touchesBegin/Move/End/Cancel these events are done in the middle of this callback.

Gesture recognition

How do you tell which gesture the user is making? Above _UIApplicationHandleEventQueue () identified 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 is registered a Observer BeforeWaiting events to monitor, 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).

CFRunloop profile

Core Foundation

Core Foundation source code view

Core Foundation source code download

CFRunLoop has five classes:

CFRunLoopRef // Contains array members of type ModeRef (_modes) and the current mode CFRunLoopModeRef // contains members of type SourceRef, TimerRef, and ObserverRef CFRunLoopSourceRef //source0,sourceCFRunLoopTimerRef // and NSTimer are toll-free bridged, can be mixed. CFRunLoopObserverRefCopy the code

toll-free bridged

The relationship between these 5 classes:

A RunLoop has a _modes member that contains modes, each of which contains Source0 / Source1 / Timer/Observer.

Runloop running mechanism

Here’s an image stolen from ibireme:

Runloop access:

Apple doesn’t allow you to create runloops directly, and only provides an API to get runloops

In the Foundation:
[NSRunLoop mainRunLoop]; // Get the main thread RunLoop object [NSRunLoop currentRunLoop]; // Get the RunLoop object for the current threadCopy the code
The Core Foundation of:
CFRunLoopRef CFRunLoopGetMain(void);
CFRunLoopRef CFRunLoopGetCurrent(void);
CFRunLoopRef _CFRunLoopGet0(pthread_t t); // private
CFRunLoopRef __CFRunLoopCreate(pthread_t t); // private
Copy the code

Runloop run:

In the Foundation:
- (void)run; // No timeout runs in NSDefaultRunLoopMode unless all timers and are removedsource- (void)runUntilDate:(NSDate *)limitDate; // also run in NSDefaultRunLoopMode - (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate; // Start with the specified Mode with a timeout limitCopy the code
The Core Foundation of:
void CFRunLoopRun(void); / / kCFRunLoopDefaultMode, 1.0 e10,false
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle);
SInt32 CFRunLoopRunSpecific(); // private
static int32_t __CFRunLoopRun(); // private
Copy the code

Runloop dormancy:

__CFRunLoopServiceMachPort(); Call mach_msg() to switch user/kernel mode

User mode -> kernel mode

Runloop stop:

  1. Remove all event sources (timer and source) from the runloop.
  2. Set the timeout period.
  3. useCFRunLoopRun()CFRunLoopRunInMode()Method to run runloop can be usedCFRunLoopStop()Stop.

Recommended reading

Understand RunLoop in depth