How do you run one shared core with several UI leaves on a single release train?
Predict first
A new core API uses a framework that does not exist on watchOS. Which process catches it before release?
// A: a UI test on the iPhone
// B: building every platform target in CI, since the unavailable-API error is a compile error
// C: user reports
- A
- B
- C
Mental model. It is a railway with one engine room (the core) and several carriages (leaves) fitted for different passengers. They all leave on the same timetable, but each carriage has its own seats, and a fault in the engine is fixed once for the whole train.
Shipping an iPhone app, an iPad layout, a Mac app, an Apple TV app and a watch app from one team is an organisation and release problem at least as much as a code problem. Repository and modules: one repository, a set of Swift packages for the core and for shared UI components (design system, feature modules with platform-neutral views), and thin app targets per platform that wire dependencies and own the platform-specific shell (navigation, input, entitlements). Enforce the direction of dependencies (leaves depend on the core, never the reverse) with module boundaries, and give platform-specific code a clear home (Sources/FeatureX/Platform/tvOS or a separate leaf package) so a reviewer can see what each platform changes. Capability tiers: decide per feature what each platform ships (full, reduced or absent) and write it down, because “parity” is rarely the right goal: the watch app shows the three things a wrist can do, the TV app is playback and browsing, the Mac app has windows and menus. Versioning and compatibility: a single version number and build train (one marketing version, per-platform build numbers), with data and API contracts versioned so older watch or TV builds work against the newer server and shared containers; watch apps lag phones, and TV and Mac users update on different rhythms, so support at least the previous release.
CI and testing: shrink the matrix. Run the core’s unit tests on one fast destination (macOS or an iOS simulator) because they are platform-independent; build every app target for every platform on each change (it catches unavailable-API errors, which are compile errors); run UI tests where behaviour differs, with the platform-specific tools (XCUITest on tvOS drives the focus engine with XCUIRemote; watch UI tests run on a watch simulator; iPad needs size-class and multitasking variants); keep a small real-device set for the things simulators cannot show (battery and background on the watch, thermal behaviour and playback on an older Apple TV, Digital Crown, haptics). Use build caching and affected-module builds (see the build topic), so a change to the TV leaf does not rebuild the watch app. Release mechanics: separate App Store Connect records for tvOS and visionOS (and a watch app embedded or standalone), different review timelines and rejection reasons per platform (tvOS focus and remote usage, watch standalone rules, Mac notarization and sandbox), phased rollouts per platform, and a single changelog with per-platform notes. Operations: per-platform crash and hang dashboards (MetricKit data tagged by platform), per-platform feature flags and kill switches, and an owner for each leaf with on-call. Governance: an architecture review for anything that adds platform-specific branches to the core, and a rule that new core APIs must compile on every supported platform before merging. The payoff of the discipline is that a bug fixed once in the core reaches every platform in the same train, while each leaf still feels native.
Unavailable APIs fail at compile time, so building every platform leaf on each change finds them immediately.
Common traps
- Chasing feature parity across platforms. A watch or TV version that mimics the phone is worse than one built for how it is used; decide capability tiers.
- Only building the iOS target in CI. Unavailable-API errors on other platforms appear late; build every leaf on every change.
They might follow up with
- How do you shrink the CI matrix? Test the core on one fast destination, build all leaves everywhere, and run UI tests only where behaviour differs.
- Why version contracts between platforms? Watch and TV builds update later than phones, so they must keep working against newer data and servers.
Related questions
- 20.1.1 How do you structure one codebase for several Apple platforms: shared cores, UI leaves, #if os, canImport and…
- 20.5.3 How is a watch app set up in Xcode alongside the iPhone app: targets, shared modules, and what races does…
- 16.6.1 A CI pipeline takes hours on a large modular app. How do you analyse and cut it: affected builds, caching…
← How do the platforms differ in input, process model and capabilities…