preface

Y God wrote is really good. Most of the content of this article comes from Y God’s in-depth understanding of RunLoop, combined with the official documents and other online materials and some of my own understanding to make some supplements and summaries, the official documents are also very worth reading.

RunLoop brief introduction

RunLoop simply translates to running a loop. What is running? Running means your program runs, loops? Well, it’s a loop. So a run loop is something that keeps your program running in a loop.

RunLoop is a mechanism that allows threads to handle events at any time without exiting. Often referred to as Event Loop, the key to implementing this model is how to manage events/messages, and how to make threads sleep when they are not processing messages to avoid resource consumption and wake up as soon as a message arrives.

So, a RunLoop is essentially 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 above. Once the thread executes this function, it will remain in a “receive message -> wait -> process” loop within the function until the loop ends (such as an incoming quit message) and the function returns.

An iOS App starts and runs on the main thread, which is suspended by the CPU after executing a task. If we don’t keep the main thread alive, our App will close as soon as it opens. So Apple enabled RunLoop by default in the main thread to keep the App running.


RunLoop with thread

Let’s just list the relationship:

  1. Runloops correspond to threads one by one.

  2. Runloops cannot be created manually and can only be obtained by methods CFRunLoopGetMain() and CFRunLoopGetCurrent().

  3. RunLoop is lazy. If you don’t use it, it won’t be created. The RunLoop in the main thread is enabled by default by Apple.

  4. The destruction of the RunLoop occurs at the end of the thread.

  5. The mapping between runloops and threads is stored in a global Dictionary

The structure of the RunLoop

There are five classes for RunLoop in Core Foundation:

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimeRef
  • CFRunLoopObserverRef

Each RunLoop contains several modes, and each Mode contains several sources/observers/timers.

Each time the main function of RunLoop is called, only one Mode can be specified. The specified Mode is called CurrentMode. This is done to separate the Source/Observer/Timer groups from each other.

CFRunLoopSourceRef is where the event is generated. Source has two versions: Source0 and Source1.

  • Source0 contains only one callback (function pointer) and does not actively fire events. To use it, you need to call itCFRunLoopSourceSignal(source), mark the Source as pending and call it manuallyCFRunLoopWakeUp(runloop)To wake up the RunLoop to handle the event.
  • Source1 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.

CFRunLoopTimeRef is a time-based trigger that can be mixed with NSTimer and Toll-free bridged. It contains a length of time and a callback (function pointer). When it is added to the RunLoop, the RunLoop registers the corresponding point in time, and when that point is reached, the RunLoop is woken up to execute that callback.

CFRunLoopObserverRef is the Observer, and each Observer contains a callback (function pointer) that the Observer receives when the state of the RunLoop changes. The following time points can be observed:

typedef CF_OPTIONS(CFOptionFlags.CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), // About to enter Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // Timer is about to be processed
    kCFRunLoopBeforeSources = (1UL << 2), // Source is about to be processed
    kCFRunLoopBeforeWaiting = (1UL << 5), // About to go to sleep
    kCFRunLoopAfterWaiting  = (1UL << 6), // Just woke up from hibernation
    kCFRunLoopExit          = (1UL << 7), // About to exit Loop
};
Copy the code

The Source/Timer/Observer above is collectively referred to as a mode item, and an item can be added to multiple modes at the same time. However, it does not work if an item is repeatedly added to the same mode. If there is no item in a mode, the RunLoop exits without entering the loop.

RunLoop Modes

For details, see the official documentation for a list of RunLoop Modes.

RunLoop:

struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set
    CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set. };Copy the code

RunLoop Mode RunLoop Mode

struct __CFRunLoopMode {
    CFStringRef _name;            // Mode Name, for example, @"kCFRunLoopDefaultMode"
    CFMutableSetRef _sources0;    // Set
    CFMutableSetRef _sources1;    // Set
    CFMutableArrayRef _observers; // Array
    CFMutableArrayRef _timers;    // Array. };Copy the code

You can download the source code for Core Foundation to see the structure in detail.

The system registers five modes by default:

  1. kCFRunLoopDefaultMode: The App’s default Mode, under which the main thread is normally run
  2. UITrackingRunLoopMode: interface tracking Mode for ScrollView tracking touch sliding
  3. UIInitializationRunLoopMode: The first Mode entered when the App is started will not be used after it is started
  4. GSEventReceiveRunLoopMode: Accepts internal modes for system events, usually not needed
  5. kCFRunLoopCommonModesThis is a placeholder Mode and has no effect

You can check out more of Apple’s internal modes here, but those modes are rarely encountered during development.

Different modes do not interfere with each other.

We commonly use two types, kCFRunLoopDefaultMode and UITrackingRunLoopMode, and a kCFRunLoopCommonModes, but kCFRunLoopCommonModes is only a pseudo-mode.

About Common Modes: A Mode can mark itself as a “Common” attribute (by adding its ModeName to RunLoop’s “commonModes”). Whenever the contents of the RunLoop change, the RunLoop automatically synchronizes the Source/Observer/Timer in _commonModeItems to all modes with the “Common” flag.

There are two preset modes in the main thread RunLoop: kCFRunLoopDefaultMode and UITrackingRunLoopMode. Both modes have been marked as “Commoc” properties. Default Mode is the normal state of App, and UITrackingRunLoopMode is tracking the state of ScrollView sliding (UITextView sliding also counts). When you create a Timer and add DefaultMode, the Timer gets repeated callbacks, but when you swipe a ScrollView, RunLoop switches mode to UITrackingRunLoopMode, In this case, the Timer will not be called back, and the sliding operation will not be affected, because different modes do not interfere with each other. Sometimes you need a Timer that gets callbacks in both modes. One way to do this is to add the Timer to both modes. Another option is to add a Timer to the top RunLoop’s commonModeItems, which are automatically updated by the RunLoop to all modes with the “Common” attribute.


CFRunLoop exposes only the following two management Mode interfaces:

CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRefmodeName, ...) ;Copy the code

Mode exposes the following interfaces for managing Mode items:

CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer,CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
Copy the code

You can only manipulate internal modes by mode name. If you pass in a new mode name and there is no mode in the RunLoop, the RunLoop will automatically create the corresponding CFRunLoopModeRef for you. For a RunLoop, internal modes can only be added, not removed.

There are two modes publicly provided by Apple: kCFRunLoopDefaultMode(NSDefalutRunLoopMode) and UITrackingRunLoopMode. You can use these two Mode names to operate the corresponding modes.

Apple also provides a string to manipulate Common flags: kCFRunLoopCommonModes(NSRunLoopCommonModes). You can use this string to manipulate Common Items or mark a Mode as “Common”. Be careful to distinguish this string from other mode names when using it.

So RunLoop Mode is customizable.

Input Sources

The input source passes events asynchronously to the thread. The source of the event depends on the type of input source and is usually one of two categories:

  • Port-based input sources (port-based input sources)
  • Custom Input Sources

Port-based input sources listen on the application’s Mach port, and custom input sources listen on custom event sources. As far as RunLoop is concerned, it doesn’t matter whether the input source is port-based or custom; the only difference between the two sources is how they signal. Port-based sources are signaled automatically by the kernel, but custom sources must be signaled manually from another thread.

When an input source is created, it is assigned to one or more modes of the RunLoop. Different modes affect listening on these input sources. Most of the time you run RunLoop in the default Mode (kCFRunLoopDefaultMode), but you can also specify a custom Mode. If the input source is not in the currently listening Mode, any events generated in it will be retained until the RunLoop is specified to run in the correct Mode.

Port-based Sources (port-based input Sources)

Cocoa and Core Foundation provide built-in support for creating port-based input sources using port-related objects and functions. In Cocoa, for example, you don’t have to create input sources directly at all. All you need to do is create a port object and add the port to the RunLoop using the methods provided by NSPort. The port object will automatically create and configure the required input source for you.

In Core Foundation, you have to manually create ports and input sources. CFMachPortRef, CFMessagePortRef, CFSocketRef are used to create the appropriate object.

Custom Input Sources

Examples of the creation and use of custom input sources can be found in the official documentation.

Cocoa Perform Selector Sources

In addition to port-based sources, Cocoa defines a custom input source that allows you to execute selectors on any thread. Like port-based sources, Perform Selector requests are serialized on the target thread, reducing many synchronization problems that can occur when multiple methods are run on one thread. Unlike port-based sources, a Perform Selector source removes itself from the RunLoop after executing its selector.

The target thread must have RunLoop enabled when another thread executes the selector. For threads you create yourself, this means explicitly starting RunLoop. Due to default launched the RunLoop in the main thread, so as long as the application calls applicationDidFinishLaunching:, you can start calling on the main thread. RunLoop processes all of the queued Perform Selector calls through a loop at a time, rather than one at a time during each loop iteration.

For methods for executing selectors on other threads, see Table 3.2 of the official documentation.

Timer Sources

It’s basically an NSTimer, a timer, and once an NSTimer registers with a RunLoop, the RunLoop registers events for its repeated time points. However, RunLoop does not call back the Timer at exactly the right time to save resources, because RunLoop has internal processing logic, which will be discussed below.

Timers are a way for a thread to tell itself to do something. For example, with a search function, we can use a timer to set a time for the user to start the search after the user starts typing, so that we can allow the user to enter as many search strings as possible before starting the search.

Although we set the timer to notify RunLoop to do something when the time is up, it doesn’t really do it in real time. Like the input source, the timer is associated with the Mode of the RunLoop. For example, if you add a timer to kCFRunLoopDefaultMode, if your Mode is UITrackingRunLoopMode, the timer will not be triggered. Unless you switch Mode to kCFRunLoopDefaultMode. If the Timer time is up and the Timer event is executed, but the RunLoop is busy doing something else, the Timer will wait until the RunLoop has done something else. If the RunLoop in the thread is not started at all, the Timer is never fired.

You can set the timer to fire only once or repeatedly. When you set the timer to fire repeatedly, the timer will fire events at the interval you set, not at the actual interval. For example, at 11:00, you set the timer event to trigger every 10 minutes, i.e. 11:10, 11:20, 11:30… If, for some reason, the event that was supposed to happen at 11:10 is delayed until 11:15, then even though you set the time interval to be 10 minutes, it looks like the next event is supposed to happen at 11:25, but it’s not, the next event happens at 11:20, The next event is triggered at 11:30 a.m. So the timer sends notifications at the time you originally planned.

Observers

The RunLoop internally handles Source events, timer-triggered events, hibernation, exit, and so on. During these specific periods, the system notifies the developer via an Observer, which associates the following moments of the RunLoop:

  • About to enter RunLoop
  • The Timer callback is about to trigger
  • The Source (non-port-based Source, Source0) callback is about to trigger
  • RunLoop is about to go to sleep
  • The RunLoop is woken up, but not before it processes the event that woke it up
  • RunLoop exit

Like the Timer, the Observer can be used once or repeatedly. A one-time Observer removes itself from the RunLoop when triggered, and you can specify whether to run it once or repeatedly when creating the Observer.

Internal logic of RunLoop

Here is the internal logic mentioned in the official document:

  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
    • Events arrive at port-based input sources (Source0)
    • Timer Indicates that the Timer is executed
    • External manual wake up
    • Time out for RunLoop
  8. Notify the Observer that the thread has just been awakened (has not yet processed the event)
  9. Process pending events
    • 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

Because Observer notifications for Timer and Source are delivered before these events actually occur, there may be a time gap between the notification event and the actual event. If the time relationship between these events is important, you can use sleep and wake sleep notifications to help you relate the time between actual events.

Inside a RunLoop is essentially a do-while loop. When you call CFRunLoopRun(), the thread stays in the loop until it times out or is stopped manually.

The underlying implementation of RunLoop

The core of RunLoop is based on Mach Port, and when it goes to sleep it calls mach_msg().

Mach itself provides very limited apis, and Apple discourages the use of Mach’s apis, but they are so basic that nothing else can be done without them. In Mach, everything was implemented through its own objects, with processes, threads, and virtual memory all referred to as “objects.” Unlike other architectures, Mach objects cannot be called directly, but can communicate with each other only through messaging. Messages are the most basic concept in Mach. Messages are passed between two ports. This is the core of IPC (inter-process communication) in Mach.

To send and receive messages, the mach_msg() function actually calls a Mach trap, the function mach_MSg_trap (), which is the equivalent of a system call in Mach. The trap mechanism is triggered when you call mach_MSg_trap () in user mode, switching to kernel mode. The mach_msg() function implemented by the kernel in the kernel state does the actual work.

If you call mach_msg() in user mode, the Mach trap will be triggered to go into the mach_msg() function called by the system to execute the actual content. About the concept of user state and kernel state, do not know the friend can baidu.

At the heart of the RunLoop is a mach_msg() function that the RunLoop calls to receive a message. If no one else sends a port message, the kernel puts the thread into a waiting state. For example, if you run an iOS App in the emulator and hit pause while the App is still, you’ll see that the main thread call stack sits at mach_MSg_trap ().

When to use RunLoop

According to the official documentation, the only time you need to use RunLoop is to create secondary Threads for your application.

The RunLoop of the main thread of the App is a critical infrastructure, so the App framework starts the main thread and enables runloops in the main thread by default at runtime. If you’re using Xcode’s template project to create your application, these systems already do it for you, and you don’t need to explicitly call it.

For secondary threads, determine if RunLoop needs to be enabled, and if so, configure and start it yourself. Under no circumstances should RunLoop be enabled for a thread. For example, if you use threads to perform some long-running, custom tasks, you can avoid starting RunLoop. (I think I’m in a bit of a fog about this, so I’ll post the original)

You do not need to start a thread’s run loop in all cases. For example, if you use a thread to perform some long-running and predetermined task, you can probably avoid starting the run loop. Run loops are intended for situations where you want more interactivity with the thread.

RunLoop needs to be started in the following situations:

  • Communicate with other threads using ports or custom input sources
  • Use timers on threads
  • useperformSelectorWhen you call another thread’s method
  • Hold the thread to perform a scheduled task

What Apple does with RunLoop

I haven’t done anything with RunLoop directly in development. You can do resident threads, but resident threads are problematic, although AFN2.0 did use them, but that was because apple’s network request framework was flawed at the time. Another place where runloop might be used is for automatic rotation, where common Mode is used, but not much else. So let’s take a closer look at Apple’s use of RunLoop.

  • AutoreleasePool
  • Incident response
  • Gesture recognition
  • Interface to update
  • The timer
  • PerformSelector
  • About the GCD
  • About Network Request

AutoreleasePool

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, which has the lowest priority and ensures that its release pool release 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 developers don’t have to explicitly create pools. Here are the steps:

  1. Coming to Loop: Create automatic release pool
  2. Thread about to sleep: Release auto release pool, create a new auto release pool
  3. Exiting Loop: Release the automatic release pool

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. Details of this process can be found here. SpringBoard only receives events such as buttons (lock screen/mute etc.), touch, acceleration, proximity sensor, etc., 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 UIGesture/processing screen rotation/send UIWindow, etc. Usually click event such as a UIButton, touchesBegin/Move/End/Cancel events are completed in the callback. Steps:

  1. Register a Source1 to receive system events
  2. Hardware event Occurrence
  3. IOHIDEvent events are generated by IOKit. Framework and received by SpringBoard
  4. SpringBoard forwards to the required App using a Mach port
  5. Registered Source1 triggers a callback
  6. In the callback, IOHIDEvent is processed and packaged as UIEvent for processing or distribution

Gesture recognition

When the above _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). Steps:

  1. Recognize gestures
  2. Calling Cancel interrupts the current touchesBegin/Move/End series of callbacks
  3. The correspondingUIGestureRecognizerMark as pending
  4. BeforeWaitingWithin its function callback, all that have just been marked as pending are retrievedGestureRecognizerAnd performGestureRecognizerThe callback

Interface to update

When in operation the UI, such as changing the frame, update the UIView/CALayer level, or manually invoked the UIView/CALayer setNeedsLayout/setNeedsDisplay method, The UIView/CALayer is marked to be processed and submitted to a global container.

Apple registers an Observer to listen for BeforeWaiting(about to go to sleep) and Exit(about to Exit Loop) events, calling back to perform a long function: _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (). This function iterates through all the UIViews/Calayers to be processed to perform the actual drawing and adjustment, and update the UI. Steps:

  1. The UI needs to be updated
  2. The UI to be updated is marked as pending and submitted to a global container
  3. BeforeWaitingExitTo perform the actual drawing and adjustment, and to update the UI interface

The timer

NSTimer is a CFRunLoopTimeRef. It’s toll-free bridged between them. Once an NSTimer is registered with a RunLoop, the RunLoop registers events for its repeated time points. Such as 10:00, 10:10, 10:20. To save resources, RunLoop does not call back the Timer at exactly the right time. The Timer has a property called Tolerance that indicates the maximum error allowed when the time point is up.

If a point in time is missed, such as when a long task is executed, the callback at that point will also be skipped without delay. Like waiting for a bus, if I’m busy with my phone at 10:10 and I miss the bus at that time, I’ll have to wait for the 10:20 bus.

CADisplayLink is a timer with the same refresh rate as the screen (but the implementation is more complicated, unlike NSTimer, which actually operates on a Source). If a long task is executed between screen refreshes, one frame will be skipped (similar to NSTimer) and the interface will feel stuck. Even a frame stalling is noticeable to the user when swiping a TableView quickly. Facebook’s open source AsyncDisplayKit is designed to solve this problem and uses RunLoop internally (mimicking the iOS interface update process).

PerformSelector

When calling NSObject performSelector: 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.

About the GCD

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.

About Network Request

IOS interface for network requests from bottom to top:

CFSocket
CFNetwork       ->ASIHttpRequest
NSURLConnection ->AFNetworking
NSURLSession    ->AFNetworking2.Alamofire
Copy the code
  • CFSocketIs the lowest level interface, only responsible for socket communication
  • CFNetworkIs based onCFSocketSuch as the upper encapsulation of the interface
  • NSURLConnectionIs based onCFNetworkA higher level of encapsulation, providing an object-oriented interface
  • NSURLSessionIt’s a new interface in iOS7, ostensibly andNSURLConnectionJuxtaposed, but still used at the bottomNSURLConnection(e.gcom.apple.NSURLConnectionLoaderThread)


The following describes the working process of NSURLConnection.

Usually with NSURLConnection, you pass in a Delegate, and when [Connection start] is called, the Delegate will receive event callbacks. In fact, the strat function takes the CurrentRunLoop internally and adds four source0s (sources that need to be triggered manually) to DefaultMode. CFMultiplexerSource handles the various Delegate callbacks, CFHTTPCookieStorage handles the various cookies.

When the network transmission, we can see NSURLConnection created two new thread: com. Apple. NSURLConnectionLoader and com. Apple. CFSocket. Private. The CFSocket thread processes the underlying socket connection. The NSURLConnectionLoader thread uses RunLoop internally to receive low-level socket events and notify the upper delegate via the previously added Source0.

The RunLoop in the NSURLConnectionLoader receives notifications from the underlying CFSocket through some Mach Port-based Source. When the notification is received, it sends the notification to the CFMultiplexerSource and other Source0 when appropriate, and wakes up the Delegate thread’s RunLoop to perform the actual callback to the Delegate.


Steps:

  1. Network transmission starts
  2. NSURLConnectionCreate two new threads,com.apple.CFSocket.privateHandle socket connections,com.aoole.NSURLConnectionLoaderInternally, RunLoop is used to accept low-level socket events
  3. An event is emitted from the socket.NSURLConnectionLoaderThis notification is received via Source1
  4. NSURLConnectionLoaderAt the right time toCFMultiplexerSource,CFHTTPCookieStorageWait for Source0 to send the notification and wake upDelegateThe thread’s RunLoop lets it handle these notifications
  5. CFMultiplexerSourceWill be inDelegateRunLoop pairs for threadsDelegatePerform the actual callback


conclusion

RunLoop is a mechanism that allows threads to handle events at any time without exiting. Often referred to as Event Loop, the key to implementing this model is how to manage events/messages, and how to make threads sleep when they are not processing messages to avoid resource consumption and wake up as soon as a message arrives.

So, a RunLoop is essentially 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 above. Once the thread executes this function, it will remain in a “receive message -> wait -> process” loop within the function until the loop ends (such as an incoming quit message) and the function returns.


A RunLoop is a thread with a RunLoop.

  1. Runloops correspond to threads one by one, and their relationship is stored in a globalDictionary
  2. RunLoop is lazily loaded, and if you don’t grab it, it will never be there
  3. The destruction of the RunLoop occurs at the end of the thread
  4. The main thread RunLoop is enabled by default


A RunLoop contains several modes, and each Mode contains several sources/timers/observers, which are collectively referred to as Mode items. Different modes do not interfere with each other.

There are five classes for RunLoop in Core Foundation:

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserver

Mode

The system registers five modes by default:

  1. kCFRunLoopDefaultMode: Default Mode of the App
  2. UITrackingRunLoopMode: Interface tracing Mode, used forScrollViewTrack touch slide
  3. UIInitializationRunLoopMode: The first Mode entered when the App is started
  4. GSEventReceiveRunLoopMode: Accepts internal modes for system events, usually not needed
  5. kCFRunLoopCommonModesThis is a placeholder Mode and has no effect

Source

There are two main types:

  • Source0: The event cannot be actively triggered
  • Source1: the thread that can actively wake up RunLoop

Specific types:

  • Port-based Sources: indicates a port-based source
  • Custom Input Sources: user-defined Input Sources
  • Cocoa Perform Selector Sources: A custom input source defined by Cocoa

Timer

Time-based trigger that registers events in the RunLoop in advance and wakes up to execute the event when the time point arrives. Limited by the internal logic of RunLoop, the timer is not very accurate.

Observer

Observer, each Observer contains a callback (function pointer).

Observable time point:

  1. KCFRunLoopEntry: the Loop is about to enter
  2. KCFRunLoopBeforeTimers: The Timer is to be processed
  3. KCFRunLoopBeforeSources: Source is about to be processed
  4. KCFRunLoopBeforeWaiting: is about to go to sleep
  5. KCFRunLoopAfterWaiting: just woke up from sleep
  6. KCFRunLoopExit: is about to exit the Loop