Runloop is a basic component that is tied to threads and is behind many of the thread-related functionality. Although we rarely use Runloop directly in our daily lives, understanding Runloop gives us a deeper understanding of iOS’s multithreaded model.
Basic concepts of Runloop
What is Runloop? Runloop is what it sounds like. It’s basically a loop, but it’s an advanced loop. While a normal while loop causes the CPU to wait in a busy state, Runloop is an “idle” wait, which is analogous to epoll under Linux. When no event occurs, the Runloop goes to sleep. When an event occurs, the Runloop goes to the corresponding Handler to handle the event. Runloop keeps threads busy when they need to do something, and puts them to sleep when they don’t.
Here’s an image from apple’s official documentation, which is used in almost every Runloop article, that shows how Runloop works in general:
The figure illustrates the role of Runloop in a thread: it receives events from the input source and timer source, and then processes them in the thread.
Runloop with thread
Runloops are tied to threads. Each thread, including the main thread, has a corresponding Runloop object. We can’t create Runloop objects ourselves, but we can get Runloop objects provided by the system.
The Runloop for the main thread will start when the application starts. The Runloop for the other threads will not start by default and will need to be started manually.
Input Source and Timer Source
Both of these are sources of Runloop events, and the Input Source can be divided into three categories
- Port-based Sources: Port events at the bottom of the system, such as CFSocketRef, are rarely used at the application layer
- Custom Input Sources, a manually created Source
- Cocoa Perform Selector Sources, a family of Methods provided by Cocoa called Perform Selector Sources, is also an event source
A Timer Source is a Timer event.
Runloop Observer
In addition to monitoring the Source to determine if there are any tasks to be done, we can also use the Runloop Observer to monitor the status of the Runloop itself. The Runloop Observer can monitor the following Runloop events:
- The entrance to the run loop.
- When the run loop is about to process a timer.
- When the run loop is about to process an input source.
- When the run loop is about to go to sleep.
- When the run loop has woken up, but before it has processed the event that woke it up.
- The exit from the run loop.
Runloop Mode
Runloop has to deal with quite a bit of complexity in monitoring and being monitored. The concept of Runloop Mode was introduced to allow Runloop to focus on the part of the task it cares about.
As shown in the figure, Runloop Mode is actually a collection of Source, Timer and Observer. Different modes isolate different groups of Source, Timer and Observer. Runloop can only run in one Mode at a time, handling the Source, Timer, and Observer in that Mode.
There are five modes mentioned in apple documentation, which are:
- NSDefaultRunLoopMode
- NSConnectionReplyMode
- NSModalPanelRunLoopMode
- NSEventTrackingRunLoopMode
- NSRunLoopCommonModes
The only publicly exposed modes in iOS are NSDefaultRunLoopMode and NSRunLoopCommonModes. NSRunLoopCommonModes is actually a collection of the Mode, including NSDefaultRunLoopMode and NSEventTrackingRunLoopMode by default.
Pits associated with Runloop
Your closest contact with runLoop in daily development is probably through NSTimer. A Timer can only be added to one RunLoop at a time. In our daily use, it is usually added to the default mode of the current runLoop, and when the ScrollView is slid, the main runLoop will go to UITrackingRunLoopMode. And at that point, the Timer won’t run.
There are two solutions:
- First: Set RunLoop modes, such as NSTimer, which we specify to run in NSRunLoopCommonModes. This is a set of modes. Once registered in this Mode, events are executed regardless of which Mode the current runLoop is running.
- Second: Another way of dealing with the Timer is that we execute and process the Timer event on another thread and then update the UI on the main thread.