Implementing Page Performance Score on iOS: PPSStateMachine, Metrics, and Instrumentation
Airbnb’s iOS Page Performance Score implementation introduces a PPSStateMachine that ties a UIViewController’s lifecycle to metric collection—tracking first layout, initial load, scroll thread hangs, additional and rich content load times—using nanosecond timestamps, state‑machine protocols, and view‑responder discovery to emit standardized performance logs.
This article is part of Airbnb's Page Performance Score series, which measures multiple user‑centric performance indicators across platforms. It introduces the definition of these metrics and their implementation on iOS.
Page System
Airbnb divides the user journey into distinct pages, each with its own Page Performance Score (PPS). On iOS, a page is associated with a UIViewController . Performance data is collected throughout the controller’s lifecycle and logged when viewDidDisappear is called. A global identifier PageName must be present for a log to be created or sent.
Instrumentation
Because instrumenting these metrics involves many edge cases, Airbnb created a state‑machine class called PPSStateMachine . The class encapsulates all logic for tracking, calculating, and emitting performance events. Engineers can obtain the PPSStateMachine linked to their UIViewController and invoke its methods in lifecycle callbacks.
State‑Machine Protocol
public protocol PPSStateMachineProtocol {
init()
func handleDidLoad()
func handleWillAppear()
func handleDidLayoutSubviews()
func handleDidAppear()
func handleDidDisappear()
func handleDidRender(isLoading: Bool, isInitialLoading: Bool?, isError: Bool)
func handleImageLoad(imageView: UIImageView, url: URL?, status: LoadingStatus)
}Time Measurement
All timestamps are measured in nanoseconds and later converted to milliseconds. Type aliases are defined to make the units explicit.
public typealias Nanoseconds = UInt64
public final class MonotonicClock {
static public func current() -> Nanoseconds {
clock_gettime_nsec_np(CLOCK_MONOTONIC)
}
}For millisecond values a computed property is used:
typealias Milliseconds = Float64
private var currentTime: Milliseconds {
Float64(MonotonicClock.current()) / 1000000.0
}Example Usage
// When the first loading indicator is set.
let currentLoadTime = LoadTime(
startTime: currentTime,
endTime: nil)
content.initialLoadTime = currentLoadTime
// One render cycle after the first content is set.
onNextRunLoop { [weak self] in
self?.content.initialLoadTime.endTime = currentTime
}View Association
Each UIViewController has an associated PPSStateMachine . Developers can override the state machine for a group of pages that share the same name. The association is discovered by traversing the view responder chain.
Version Control
Declaring lifecycle and semantic methods in the PPS protocol abstracts the scoring calculation. Most updates to the PPS formula do not require changes in the consuming code. Major changes are first tested by emitting potential values in log metadata; once validated, they become the official scoring values.
Metric Definitions
First Layout Time (TTFL) : measured from viewDidLoad to the end of the first viewDidLayoutSubviews .
Initial Load Time (TTIL) : measured from viewDidLoad until the end of the first render cycle after content is loaded.
Scroll Thread Hang (STH) : reports the duration of UI thread stalls that exceed a threshold based on the device’s refresh rate. Stalls are detected using CADisplayLink in RunLoop.Mode.tracking , comparing consecutive frames.
Main Thread Hang (MTH) : tracked on iOS but omitted because its measurement adds overhead without providing additional perceptual value.
Additional Load Time (ALT) : starts when a loading indicator appears and ends after the indicator disappears and a render cycle completes. In infinite‑scroll scenarios, ALT measures the time from the visible loading indicator to the completion of the next page load.
Rich Content Load Time (RCLT) : measured via a custom URLImageView abstraction that hides the metric from engineers. RCLT tracks the time the loading placeholder is visible; when the image finishes loading, the state machine records the duration, discarding the URL if the duration is below a threshold to keep logs small.
Summary
The iOS implementation of PPS enables engineers to quickly collect real‑world performance data. Airbnb continues to evolve the tooling and infrastructure, encouraging other companies to adopt and extend these research outcomes.
Airbnb Technology Team
Official account of the Airbnb Technology Team, sharing Airbnb's tech innovations and real-world implementations, building a world where home is everywhere through technology.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.