Mobile Development 20 min read

Unlocking iOS Event Handling & Rendering: RunLoop, UI Threads, and Core Animation

This article explains iOS’s event processing pipeline—from hardware events through the RunLoop’s observer, source, timer, and dispatch mechanisms—detailing why UI must run on the main thread, how CALayer and Core Animation render, and the roles of CADisplayLink, NSTimer, and async display techniques.

WeChat Client Technology Team
WeChat Client Technology Team
WeChat Client Technology Team
Unlocking iOS Event Handling & Rendering: RunLoop, UI Threads, and Core Animation

iOS RunLoop Overview

RunLoop is a loop that receives and processes asynchronous events: it waits for events and dispatches them to appropriate handlers.

Below is a simplified illustration of how a touch event travels from the OS layer to the app's main RunLoop.

Simple RunLoop code example:

<code>int main(int argc, char * argv[]) {
    while (AppIsRunning) {
        id whoWakesMe = SleepForWakingU p();
        id event = GetEvent(whoWakesMe);
        HandleEvent(event);
    }
    return 0;
}</code>

RunLoop handles six categories of events:

<code>static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__();
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__();</code>

Observer events – notifications of RunLoop state changes (e.g., used for main‑thread lag monitoring).

Block events – immediate NSObject PerformSelector calls, dispatch_after, and block callbacks.

Main dispatch queue events – GCD blocks dispatched to the main queue.

Timer events – delayed PerformSelector, dispatch_after, and NSTimer callbacks.

Source0 events – handling UIEvent, CFSocket, etc.; manually triggered.

Source1 events – handling kernel mach_msg events (e.g., CADisplayLink).

Pseudocode of RunLoop execution order:

<code>SetupThisRunLoopRunTimeoutTimer(); // GCD timer
do {
    __CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
    __CFRunLoopDoObservers(kCFRunLoopBeforeSources);
    __CFRunLoopDoBlocks();
    __CFRunLoopDoSource0();
    CheckIfExistMessagesInMainDispatchQueue();
    __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);
    var wakeUpPort = SleepAndWaitForWakingUpPorts();
    __CFRunLoopDoObservers(kCFRunLoopAfterWaiting);
    if (wakeUpPort == timerPort) {
        __CFRunLoopDoTimers();
    } else if (wakeUpPort == mainDispatchQueuePort) {
        __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
    } else {
        __CFRunLoopDoSource1();
    }
    __CFRunLoopDoBlocks();
} while (!stop && !timeout);
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopExit);
</code>

Understanding this order explains why the following code can detect when a table view reload finishes:

<code>dispatch_async(dispatch_get_main_queue(), ^{
    _isReloadDone = NO;
    [tableView reload]; // triggers layoutIfNeeded at the end of the RunLoop
    dispatch_async(dispatch_get_main_queue(), ^{
        _isReloadDone = YES;
    });
});</code>
Two tasks are inserted into the main queue; the RunLoop gets a chance to execute them both—once before sleeping and once after waking.

Why UI Must Run on the Main Thread

UIKit is not thread‑safe. Simultaneous modifications from multiple threads can cause crashes, visual glitches, or inconsistent view hierarchies. Although many drawing classes have become thread‑safe since iOS 4, UI updates should still be performed on the main thread.

Event Response

iOS registers a Source1 (mach‑port based) to receive system events. When a hardware event occurs, IOKit creates an IOHIDEvent, SpringBoard forwards it via a mach port to the app, and the Source1 callback invokes

_UIApplicationHandleEventQueue()

, which wraps the event into a UIEvent and dispatches it to the appropriate responder.

CALayer

Every UIView has a backing CALayer. CALayer manages visual content, transformations, and animations but does not handle user interaction. The system maintains three parallel trees for a layer: the model tree (property values set by code), the presentation tree (interpolated values during animations), and the render tree (values actually displayed on screen).

CADisplayLink and NSTimer

NSTimer is a

CFRunLoopTimerRef

registered with a RunLoop; it fires at scheduled intervals but may be delayed by tolerance settings. CADisplayLink synchronizes callbacks with the display’s refresh rate (typically 60 Hz) and is implemented as a RunLoop source.

iOS Rendering Process

The CPU prepares drawing commands, submits them to the GPU, which renders into a framebuffer. VSync signals trigger the display controller to read the framebuffer. If the CPU or GPU cannot finish before the next VSync, the frame is dropped, causing stutter.

Core Animation registers an observer for

BeforeWaiting

and

Exit

events. When UI changes occur, the layer is marked as dirty; the observer’s callback commits the transaction, causing the GPU to render the updated content.

CPU vs. GPU Rendering

GPU rendering can be on‑screen (directly to the display buffer) or off‑screen (to an intermediate buffer). Off‑screen rendering incurs extra buffer allocation and context switches, and is triggered by properties such as

shouldRasterize

,

masks

,

shadows

,

edge antialiasing

, and

group opacity

.

Core Animation

Implicit animations are automatically generated by the framework; any property change within a RunLoop cycle is animated over ~0.25 s. Explicit animations (e.g.,

CABasicAnimation

,

CAKeyframeAnimation

) are created and added to a layer manually.

<code>@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *layerView;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"Outside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
    [UIView beginAnimations:nil context:nil];
    NSLog(@"Inside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]);
    [UIView commitAnimations];
}
@end
</code>

Facebook Pop Introduction

Pop provides a flexible animation system that can target any

NSObject

, not just

CALayer

. It uses

CADisplayLink

under the hood and allows custom read/write blocks for animating arbitrary properties.

<code>prop = [POPAnimatableProperty propertyWithName:@"com.foo.radio.volume" initializer:^(POPMutableAnimatableProperty *prop) {
    prop.readBlock = ^(id obj, CGFloat values[]) { values[0] = [obj volume]; };
    prop.writeBlock = ^(id obj, const CGFloat values[]) { [obj setVolume:values[0]]; };
    prop.threshold = 0.01;
}];
POPSpringAnimation *anim = [POPSpringAnimation animation];
anim.property = prop;
</code>

AsyncDisplay Introduction

AsyncDisplayKit (now Texture) creates

ASDisplayNode

, a thread‑safe abstraction of

UIView

/

CALayer

. Nodes can be configured off the main thread; the underlying view/layer is instantiated lazily on the main thread, enabling background layout and rendering.

Reference Articles

RunLoop principles – https://github.com/ming1016/study/wiki/CFRunLoop

Deep dive into RunLoop – http://blog.ibireme.com/2015/05/18/runloop/

Thread‑safe class design – http://objccn.io/issue-2-4/

iOS smooth UI techniques – http://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/

Off‑screen rendering – http://foggry.com/blog/2015/05/06/chi-ping-xuan-ran-xue-xi-bi-ji/

Advanced iOS Core Animation – https://zsisme.gitbooks.io/ios-/content/index.html

renderingiOSRunLoopUI ThreadCore AnimationCADisplayLinkAsyncDisplay
WeChat Client Technology Team
Written by

WeChat Client Technology Team

Official account of the WeChat mobile client development team, sharing development experience, cutting‑edge tech, and little‑known stories across Android, iOS, macOS, Windows Phone, and Windows.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.