First put the apple doc, the translation of this article is basically controlled doc: developer.apple.com/library/mac…
RunLoops are the underlying foundation for thread loops. It is essentially a loop that runs as it is meant to, or rather a loop in a thread. It is used to receive loop events and schedule work for the thread, and to put the thread to sleep when there is no work in the thread.
RunLoops is not completely automated. You can use Runloop to help you handle incoming events in new threads you’ve created. The Cocoa and CoreFoundation frameworks in iOS have full apis for manipulating Runloop objects. In the main thread the RunLoop is created by the system, in the child thread you must manually generate a RunLoop.
Related content: NSRunLoop CFRunLoop
What is a RunLoops
In the new Xcode production project there are the following code blocks:
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([YourAppDelegate class])); }}Copy the code
When the program starts, the above code is called, the main thread starts, and the RunLoop starts. After the program is initialized in the UIApplicationMain() method and the program’s Delegate task is set, the main thread RunLoop is opened, and event handling begins.
A RunLoop is a loop in which it takes input from the thread and processes events through event handlers. Your code should provide a while or for loop to drive runloop. In your loop, use the Runloop object to drive the event to handle the relevant content, receive the event, and handle the response.
RunLoop accepts two main types of event sources: asynchronous input sources and synchronous Timer sources. The handling methods of these two events are specified by the system.
The official documentation has the following map, which explains the basic structure of runloop
As you can see from the diagram, a RunLoop is a loop in a thread that handles the events it receives. Our code can drive RunLoop by providing a while or for loop. In the Loop, the Run Loop object is responsible for the code that handles the event (accepts the event and calls the corresponding event handling method).
RunLoop receives messages from two different event sources:
- InputSources: Used to deliver asynchronous messages, usually from another thread or program. Upon receiving the message and calling the specified method, the thread’s corresponding NSRunLoop object exits by executing the runUntilDate: method.
- Timer Source: Used to deliver synchronization messages in Timer events (Schedule or Repeat). RunLoop does not exit during message processing.
- In addition to handling the above two types of Input Soruce, RunLoop also generates different notifications during execution, identifying the state of the RunLoop, so you can register an Observer for RunLoop to monitor the RunLoop Apple only provides the Core Foundation API to register an Observer for the RunLoop.
The next two sections mainly cover the components of RunLoop, the modes of RunLoop, and the notifications generated during RunLoop execution
RunLoopModes– The mode in which runloops run
The RunLoopMode can be understood as a collection of all the event sources to monitor (the two mentioned earlier) and observers registered in the RunLoop to be notified. Each time a RunLoop is run, it needs to be explicitly or implicitly specified which Mode it is running in (a RunLoop can only run in one Mode at a time). Once RunLoopMode is set, your RunLoop will automatically filter other mode-related event sources, and only monitor the sources related to the current Mode (and notify relevant observers). Most of the time RunLoop runs on the default mode defined by the system.
In code, you can distinguish between modes by mode name. The Cocoa & CoreFoundation framework defines the default mode and a series of other modes by different names (NSString,CFString). You can also use a different name, define your own mode, and add sources and observers to that mode.
Use these modes to filter events from unwanted event sources. In most cases, we set runloop to default mode.
The RunLoopMode is determined based on the event source, not the event type. For example, you can’t use RunLoopMode to select only mouse click events or keyboard input events. You can use RunLoopMode to listen for ports, suspend timers, or change the way you add or remove sources or observers in modes.
Cocoa and CoreFoundation define the default and common mode. the name of the RunLoopMode can be identified by a string, or we can use a string to specify a Mode name to customize the Model.
Here are the runloopmodes defined in iOS:
- NSDefaultRunLoopMode kCFRunLoopDefaultMode: most of the work in the default operating mode.
- NSConnectionReplyMode: Use this Mode to listen for the state of NSConnection objects. We rarely need to use this Mode ourselves.
- NSModalPanelRunLoopMode: Use this Mode to distinguish events in the case of a Model Panel (encountered in OS X development).
- UITrackingRunLoopMode: Use this Mode to track events from user interactions (such as UITableView sliding up and down).
- GSEventReceiveRunLoopMode: used to accept the system events, Run within the Loop Mode.
- NSRunLoopCommonModes, kCFRunLoopCommomModes: This is a pseudo-mode that is a set of RunLoop modes. Adding an Input source to this mode means associating the Input source with all Modes included in Common Modes. In iOS, NSRunLoopCommonMode includes NSDefaultRunLoopMode, NSTaskDeathCheckMode, and UITrackingRunLoopMode. Also, we can use the CFRunLoopAddCommomMode() function in CoreFoundation to add custom modes.
Note that RunLoop can only run in a fixed Mode, and only the Timer source and Input source added in this Mode are monitored. If no time source is added in this Mode,RunLoop returns immediately. RunLoop cannot run in NSRunLoopCommonModes because NSRunLoopModes are a set of modes, not a specific Mode. NSRunLoopCommomModes can be used when adding event sources. If RunLoop is in any Mode of NSRunLoopModes, the event source will be fired.
The event source of RunLoop
Input Soruces sends events asynchronously to Threads. The event source is based on the type of input source. There are two different types of Input sources: Port-based Sources and Custom Input Sources. Port-based Sources listens on Mach ports, and the Custom Input Source type monitors the Mach Port emitted by the Custom Source event. The difference between the two types of Input Sources is that the port-based Sources are automatically sent from the kernel, while the custom Sources must be manually sent from other threads
When you create an input soruce, you need to put it into one or more Runloop modes. If an input source is not in the currently monitored mode, the event Runloop generated by the input source will not be received unless the input source is in the correct mode.
Several different input sources are described below.
Port-Based Sources
The Cocoa and CoreFoundation frameworks provide built-in support for port-based input soruces related objects and functions. For example, in Cocoa, you never have to create an input source directly. You can create an NSPort directly through the method and add the port object directly to the runloop. The NSPort object is responsible for creating and configuring the input source itself.
In CoreFoundation, you must manually create a port and its corresponding Runloop source. Using CFMachPortRef CFMessagePortRef, CFSocketRef to create the appropriate objects.
Example of creating port-based soruCE is as follows:
Developer.apple.com/library/mac…
Custom Input Source
We can use the CFRunLoopSourceRef type-specific functions in CoreFoundation to create custom Input Sources. CoreFoundation invokes the registered callback function on several nodes triggered by different events: Configuration, Handle incoming Events, and destroy sources.
At the same time, you must define the behavior of the Custom Source when an event arrives and how the event is delivered.
Specific invocation instance: developer.apple.com/library/mac…
Cocoa Perform Selector Sources
The Cocoa framework defines some Custom Input Sources for us that allow usto perform a Selector method on any thread.
When performing a selector in a target thread, the thread’s runloop must be running. This means that if the thread is created by you, the selector will not be executed until the Runloop starts, and the selector that was previously added by Perform selector will be added to a queue waiting to be executed. Like port-based Sources, these selector requests are serialized to a queue in the target thread to alleviate synchronization problems caused by multiple method executions in the thread. Unlike port-based Sources, a selector method is automatically removed from the current RunLoop when executed.
So here’s the method for Perform Selector that’s defined in NSObject
/ / in the main thread of the RunLoop perform specified @ the selector method performSelectorOnMainThread: withObject: waitUntilDone: performSelectorOnMainThread:withObject:waitUntilDone:modes: / / under the current thread RunLoop lazy loading specified @ the selector methods performSelector: onThread: withObject: waitUntilDone: performSelector:onThread:withObject:waitUntilDone:modes: / / under the current thread RunLoop lazy-loading perform @ the selector methods performSelector: withObject: afterDelay: PerformSelector: withObject: afterDelay: inModes: / / cancel the current thread calls cancelPreviousPerformRequestsWithTarget: cancelPreviousPerformRequestsWithTarget:selector:object:Copy the code
Timer sources
Asynchronously sends evet to a thread at a preset point in time.Timer sources is a way for a thread to tell itself to do something. For example, search filed can use a timer to automatically query the information entered by users at regular intervals.
Although Timer Sources generates time-based notifications, Timer is not completely timepoint-based. Like the input source, the Timer is associated with a specific RunLoopMode. If the Timer is not monitored by the mode of the current RunLoop, the Timer will not be triggered until the RunLoop switches to the mode of the Timer. Similarly, if the Timer is fired in the current mode, but the RunLoopMode is changed, the subsequent Timer will still not be fired.
We can set the Timer to be triggered either once or repeat. A repeating timer is based on the previous fire time, not the actual time (there may be a change in the runloopmode). For example, if a timer is set to trigger every 5s, each activation will be the previous one plus 5s, even if the previous fire time is delayed. Even if one fire time is delayed for a long time, maybe 13s, and two fire times are missed, the fire time will still be fired once later. After the timer fires this time, the timer replans the next fire time to be 5s later.
Cocoa and CoreFoundation NSTimer,CFRunLoopTimer provide methods to set Timer sources. It’s important to note that in addition to scheduledTimerWithTimeInterval at the beginning of the method to create the Timer will need to manually added to the current RunLoop. (scheduledTimerWithTimeInterval Timer automatically to create Default mode loaded into current RunLoop)
After a Timer is selected to be used once, it is removed from the RunLoop at the end of execution. When a loop is selected, it is saved in the current RunLoop until the invalidated method is executed.
Specific use the timer instance: developer.apple.com/library/mac…
Observers RunLoop Observers, Observers of the state of the RunLoop process
The synchronous “timer” or asynchronous custom Source that activates the Input source of the RunLoop mentioned above, Observers of Runloop will be automatically triggered when the runloop is in a state. 30. You can use observers for an action when runloop is about to handle an event, or they can control threads when runloop is about to sleep. In short, the RunLoop Observer is notified when the RunLoop itself enters a state
You can create an Observer to observe the following state of the RunLoop:
- When RunLoop enters
- RunLoop is about to handle a Timer
- RunLoop is going to process an Input Source
- RunLoop is about to go to sleep
- RunLoop is about to be woken up before the event to wake it up is processed
- When the RunLoop stops
You can add observers to RunLoop using the CoreFoundation API. Create an instance of a Runloop Observer using CFRunLoopObserverRef. At instance creation time, configure callbacks to the Observer to track the status of the Runloop.
Similar to timer, runloop observers can be declared once or repeat monitors. A one-time observer is removed from the runloop when it is triggered, and the repeat type continues to listen. When you create a observers, you should specify once or repeat.
The code examples: developer.apple.com/library/mac…
RunLoop Sequence of events
Every time, RunLoop operates on the incoming events(generated by various input sources) and generates notifications(for observers). RunLoop handles events and emits notifications The specific order is as follows: 1> Send a notification to the Observer when the RunLoop starts. 3> Send notification to the Observer that another non-port-based Input Source is about to be triggered 5> Handle the event immediately if the port-based Input source event source is about to trigger. 6> Send notification to the Observer that the current thread is about to go to sleep. 7> Put the thread to sleep until the following event occurs: 1. Port-based Input Source event 2.Timer Fires 3. Thread will wake up 9> handle event 1 raised. If it is triggered by a user-defined Timer, restart the Run Loop after processing the Timer event function. Go to Step 2. 2. If the event source triggers an event, send the message 3. If runloop is waking up and there is no timeout, restart runloop. Go to Step 2. 10> Send notification to the Observer that Run Loop has exited
Because the timer and input source cause runloop to send notification to the Observer before these events, there may be a short interval between notification sending and actual event occurrence. The exact time can be fixed via awake-from-sleep’s notification.
Because timers and other time-slice-related events are delivered while runloop is running, loop delivery of these events can sometimes fail. A typical example would be when you enter runloop, listen for the mouse’s movement path, and then repeatedly request events in your app. Because your code already holds these events and handles them directly, rather than having the app dispatch events normally, active timers cannot be fired until your mouse tracking events stop and hand control to the app.
RunLoop can be invoked using the RunLoop object display. Other events can also wake up a runloop. For example, adding a non-port-based input source to a runloop can wake it up without waiting for other events to occur.
When to use RunLoop
We should only run a RunLoop when we create a helper thread. The iOS app will run a RunLoop for me when the app starts, but our new helper thread will not. For the worker thread, we still need to decide if we need to start a RunLoop. For example, if we use a thread to handle a predefined long task, we should avoid starting RunLoop. Here are a few scenarios for using RunLoop provided by the official Document:
- If you need to use a Port-based InputSource or a Custom InputSource to communicate with other threads
- You need to use a Timer in a thread
- You need to use the threads mentioned above
selector
Related methods - The thread needs to perform some work periodically
If you choose to use RunLoop, RunLoop setup and startup are fairly straightforward. At the same time, you need to implement when to exit the runloop from the worker thread. It is best not to close the thread directly, but to exit the runloop first.
How to create and set the runloop. Code: developer.apple.com/library/mac…
Use RunLoop objects
The RunLoop pair provides the primary interface for adding input sources,timers, and observers. Each thread has one and only one runloop. NSRunLoop is used in Cocoa and CFRunLoopRef is used in CoreFoundation.
Get the RunLoop object from the thread
To get a Runloop object from the current thread, do the following:
- In Cocoa, using [NSRunLoop currentRunLoop] returns the runLoop object of the current thread.
- CFRunLoopRef is used in CoreFoundation.
CFRunLoopRef and NSRunLoop can be converted, and NSRunLoop uses getCFRunLoop to get the CFRunLoopRef object
Configure the RunLoop object
You must add at least one input source or timer to the runloop before the worker thread can start it. If a runloop does not have an event source in it, the runloop will exit as soon as you start it.
Once you have added source, you can add observers to runloops to monitor the state of various runloop implementations. To join an Observer, you should create a CFRunLoopObserverRef and add an Observer to your runloop using the CFRunLoopAddObserver function.
The following code block shows how to add an observer to RunLoop.
- (void)threadMain { // The application uses garbage collection, so no autorelease pool is needed. NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop]; // Create a run loop observer and attach it to the run loop. CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL}; CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context); if (observer) { CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop]; CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode); } / / Create and schedule the timer. [NSTimer scheduledTimerWithTimeInterval: 0.1 target: the self selector:@selector(doFireTimer:) userInfo:nil repeats:YES]; NSInteger loopCount = 10; do { // Run the run loop 10 times to let the timer fire. [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; loopCount--; } while (loopCount); }Copy the code
Start the RunLoop
In the worker thread, it is necessary to start a runloop. The runloop must have an input source or timer event source. If no input source or timer is listened for internally when the runloop starts, the runloop immediately exits.
There are several ways to start RunLoop:
- No specific conditions are set for startup
- Set a time limit
- In a particular mode
The simplest is an unconditional start runloop, but this is also the worst option. If no conditions are set, the thread on which the runloop is located is looped permanently. You can increase or decrease input sources, timers, but there is only one way to kill it. It also does not allow runloops to run in custom mode.
A better alternative to entering the runloop unconditionally is to run the runloop with a preset timeout period, so that the runloop runs until an event arrives or the specified time expires. If an event arrives, the message is passed to the appropriate handler for processing, and the runloop exits. You can restart runloop to wait for the next event. If the specified time is up, you can simply restart runloop or use the time to do any other work.
In addition to the timeout mechanism, you can also run your runloop in a specific mode. Modes and timeouts are not mutually exclusive and can be used at the same time when starting runloop. Patterns restrict the types of input sources that can pass events to run loop, as described in the “Run Loop Patterns” section.
Describes the architecture of the main routines of threads. The key to this example is to illustrate the basic structure of runloop. Essentially you add your own input source or timer to the Runloop and then repeatedly call a program to start the Runloop. Each time a runloop returns, you need to check to see if there are any conditions that will cause the thread to exit. The Core Foundation run Loop routine is used in the example so that you can examine the return result to determine why the run loop exits. In Cocoa programs, you can also run the loop using the NSRunLoop method without checking the return value.
- (void)threadMain { // The application uses garbage collection, so no autorelease pool is needed. NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop]; // Create a run loop observer and attach it to the run loop. CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL}; CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context); if (observer) { CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop]; CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode); } / / Create and schedule the timer. [NSTimer scheduledTimerWithTimeInterval: 0.1 target: the self selector:@selector(doFireTimer:) userInfo:nil repeats:YES]; NSInteger loopCount = 10; do { // Run the run loop 10 times to let the timer fire. [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; loopCount--; } while (loopCount); }Copy the code
You can run the run loop recursively. In other words, you can use CFRunLoopRun, CFRunLoopRunInMode, or any NSRunLoop method to start the Run loop in the input source or timer handler. In doing so, you can start nested run loops using any mode, including the mode used by the outer run loop.
Exit the RunLoop
There are two ways to exit a runloop:
- Set a timeout event for runloop
- The first method is recommended if you can configure it. Specifying a timeout allows the Run loop to complete all normal operations, including sending messages to run loop observers, before exiting.
Explicitly stopping the runloop using CFRunLoopStop produces similar results as using a timeout. Runloop sends all remaining notifications before exiting. Instead of setting timeouts, you can use this technique in run loops that start unconditionally.
Although removing a runloop’s input source and timer can also cause a Run loop to exit, this is not a reliable way to exit a runloop. Some system routines add input sources to the Runloop to handle the required events. Because your code does not necessarily take these input sources into account, you may not be able to remove them from your system routines, resulting in an exit from Runloop.
Thread safety and RunLoop objects
Threads are safe depending on which apis you use to manipulate your Runloop. Functions in CoreFoundation are generally thread-safe and can be called by any thread. However, if you modify the runloop configuration and need to perform some operations, you should always perform those operations on the thread that the Run loop belongs to.
The NSRunLoop class of Cocoa does not have the innate thread-safety of CoreFoundation. If you want to modify your runloop using the NSRunLoop class, you should do so in the thread that the runloop belongs to. Adding input sources and timers to runloops that belong to different threads can cause your code to crash or cause unpredictable behavior. (Do not operate on other threads’ runloops in the current thread.)
Configuration RunLoop sources
The following sections give examples of how to set up different types of input sources in Cocoa and Core Foundation
Custom Input Sources
Creating a custom input source includes defining the following:
- The input source needs to process information
- A scheduler that lets interested clients (presumably other threads) know how to interact with the input source
- A program that handles requests sent by any other client (interpreted as any other thread)
- A cancellation procedure that invalidates an input source
Since you create your own input sources to handle custom messages, the actual configuration options are flexible. The scheduler, handler, and canceller are all critical routines for creating custom input sources. However, most of the other behavior of the input source occurs outside of these routines. For example, you decide the mechanism by which data is transferred to the input source, and the mechanism by which the input source communicates with other threads.
Ps: Custom source is rarely used… See www.dreamingwish.com/article/ios…
Configure the Timer source
To create a timer source, all you need to do is create a timer object and schedule it to your runloop. Cocoa programs use the NSTimer class to create a new timer object, while Core Foundation uses the CFRunLoopTimerRef opaque type. Essentially, the NSTimer class is a simple extension of CoreFoundation that provides convenient features, such as the ability to create and deploy timers in the same way. Cocoa can use the following NSTimer class methods to create and deploy a timer:
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
scheduledTimerWithTimeInterval:invocation:repeats:Copy the code
The above method creates timers and adds them to the current thread’s Run loop in default mode. You can manually create an NSTimer object and add it to the Run loop via the NSRunLoop addTimer:forMode:. Both methods do the same thing, but the difference is the control you have over the timer configuration. For example, if you manually create a timer and add it to a Run loop, you can choose the mode to add instead of using the default mode. The dia below shows how to create timers using these two methods. The first timer starts 1 second after initialization and runs every 0.1 second thereafter. The second timer starts 0.2 seconds after initialization and runs every 0.2 seconds thereafter.
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop]; / / Create and schedule the first timer. NSDate * futureDate = [NSDate dateWithTimeIntervalSinceNow: 1.0]; NSTimer * myTimer = [[NSTimer alloc] initWithFireDate: futureDate interval: 0.1 target: the self selector:@selector(myDoFireTimer1:) userInfo:nil repeats:YES]; [myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode]; / / Create and schedule the second timer. [NSTimer scheduledTimerWithTimeInterval: 0.2 target: the self selector:@selector(myDoFireTimer2:) userInfo:nil repeats:YES];Copy the code
The following code shows the code to configure the timer using the Core Foundation function. Although this example does not use any user-defined information as a context structure, you can use this context structure to pass any information you want to the timer. For more information about the contents of this context structure, see the CFRunLoopTimer Reference.
CFRunLoopRef runLoop = CFRunLoopGetCurrent(); CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL}; CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0, &myCFTimerCallback, & Context); CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);Copy the code
Configure a Port-based source
Both Cocoa and Core Foundation provide port-based objects for communication between threads or processes. The following sections show how to establish port communication using several different types of port objects.
Configure the NSMachPort object
To establish a stable local connection with the NSMachPort object, you need to create the port object and add it to the corresponding thread’s Run loop. When running a worker thread, you pass a port object to the thread’s main entry point. The worker thread can use the same port object to return the message to the original thread. Ps: There is not much communication between processes. AFNetworking 2.x is used. To prevent the runloop from stopping, add an NSPort source before starting the runloop.
Reference: developer.apple.com/library/mac… Chun. Tips/blog / 2014/1… www.dreamingwish.com/frontui/art… Github.com/wuyunfeng/L… Github.com/yechunjun/R… Blog.ibireme.com/2015/05/18/…