Mobile Development 9 min read

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.

Airbnb Technology Team
Airbnb Technology Team
Airbnb Technology Team
Implementing Page Performance Score on iOS: PPSStateMachine, Metrics, and Instrumentation

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.

Mobile DevelopmentInstrumentationiOSMetricsPerformance MonitoringSwiftPage Performance Score
Airbnb Technology Team
Written by

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.

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.