A repeated compile dominates the timeline; the fastest fix is to trial Tuist Xcode Cache before buying or adding another runner.
A remote Mac queue, Simulator test, or signing step dominates the timeline; add or split remote Mac capacity instead. If both symptoms appear, keep the cache and expand the Mac CI pool in parallel.
This runbook is for:
- Developers on large iOS or macOS projects who rebuild the same dependencies and source files.
- DevOps engineers maintaining self-hosted Mac CI and separating build time from runner wait time.
- Platform owners deciding whether to optimize existing nodes, rent more capacity, or maintain two execution paths.
Last updated: September 15, 2026. Technical behavior and current guidance were checked against the Tuist Xcode Cache guide, the Tuist cache change record dated September 8, 2026, Apple’s Xcode incremental build guidance, and the relevant CI runner documentation.
SECTION 01Start with the time that is actually slow
“Xcode is slow” is not a useful diagnosis. A pipeline can spend its time in at least five different places:
- Waiting for an available Mac runner.
- Compiling source and dependencies.
- Uploading or downloading cached artifacts.
- Running Simulator or UI tests.
- Creating an archive and signing it.
Tuist Xcode Cache can address reusable Xcode compilation work. It cannot make an occupied runner available, make a UI test complete without executing the app, or remove the requirements of an archive and signing environment. Apple’s build guidance also treats incremental compilation as a function of project structure and build inputs, not as a universal switch that makes every build fast. See the Apple explanation of incremental build performance.
What does Tuist Xcode Cache actually help?
It is a good candidate when the same project repeatedly recompiles the same eligible inputs under a stable toolchain, target configuration, architecture, and environment. In that case, the cache can share Xcode compilation products instead of rebuilding identical work.
It is a weaker candidate when most time comes from dependency resolution, custom scripts, code generation, packaging, test execution, or signing. Those stages may surround compilation without becoming reusable cache work.
Which build types can benefit from Tuist Xcode Cache?
Repeated clean or semi-clean compilation of stable targets is the clearest fit. Incremental builds can also benefit when their inputs remain identical enough for reuse. The cache is not a blanket accelerator for every command in an Xcode pipeline. Treat each stage as a separate measurement category.
Evidence to collect before changing the pipeline
Use Xcode’s Build Timing Summary for a representative build, then save records for repeated builds of the same commit. Do not compare an interactive local build with an unrelated release archive. The comparison must preserve the project revision, Xcode toolchain, SDK, target, architecture, build configuration, and script inputs.
Record these fields:
- Queue start and runner allocation time.
- Build start and compile completion time.
- Cache lookup, upload, and download time.
- Test start and test completion time.
- Archive, signing, and artifact publication time.
- Whether the job ran in a clean environment or reused a workspace.
The Tuist Cache guide documents the cache setup, CI requirements, upload behavior, and cache hit states. Use those documented states rather than inventing a target hit-rate threshold. There is no universal hit-rate number that proves the feature is worthwhile for every project.
SECTION 02First step: prove whether the cache has a fair chance
Before enabling a cache experiment, inspect the inputs that define the compilation result. A cache miss can be correct behavior, not a broken service.
| Input to compare | What to inspect | Decision if it differs |
|---|---|---|
| Source and dependency revision | Same commit, lockfile, generated sources, and dependency graph | Reproduce the same revision before judging reuse |
| Toolchain and SDK | Xcode version, SDK, compiler settings, and selected platform | Align the toolchain or treat the result as a separate cache population |
| Target and architecture | Same scheme, target, build configuration, and architecture | Separate incompatible jobs instead of combining their results |
| Environment paths | Absolute paths, generated files, credentials, and script environment | Remove unstable inputs or expect misses |
| CI identity and service access | Authentication, remote endpoint, and upload permissions | Fix connection or authorization failures before performance analysis |
Tuist’s CI documentation explains authentication requirements for automated workflows in more detail in its continuous integration guide. A successful local command does not prove that the CI identity can read from or write to the same remote cache.
When should you enter a cache trial, and when should you fix the project first?
Start the trial when repeated compilation is a measurable share of execution time and the same inputs recur across jobs. Fix the project or pipeline first when generated sources change unexpectedly, build settings vary by runner, or scripts write unstable data into the build inputs. Otherwise, the cache will expose reproducibility problems without solving them.
Separate local hits, remote hits, and misses
A useful report does not only say “cache enabled.” It distinguishes:
- A local hit from artifacts already present on the current machine.
- A remote hit from artifacts fetched from the shared cache.
- A miss caused by a changed input.
- A miss caused by unavailable or unauthorized cache access.
- A miss caused by an unsupported or intentionally excluded task.
This distinction prevents a misleading success report. A local hit may disappear when the job moves to another runner. A remote hit may save compilation time but add network transfer time. An authentication failure can look like an ordinary miss if the logs are not inspected.
SECTION 03Second step: check whether transfer cancels the saving
A cache that saves CPU work can still fail the delivery objective if its artifacts take too long to upload or download. The relevant comparison is not “compile time before versus compile time after.” It is:
effective build time = queue wait + compilation + cache transfer + scripts + tests + archive/signing
Keep these terms separate in your job log. If the compiler phase falls but transfer grows by the same amount, the cache is not yet a net improvement for that route.
Tuist’s September 8, 2026 change record describes chunk reuse in its newer transfer mechanism. That update supports a narrower conclusion: reusing unchanged chunks can reduce repeated transfer work in eligible cases. It does not establish one universal speedup for every repository, network path, artifact size, or runner region. Read the Tuist chunk reuse change record alongside your own transfer logs.
A transfer-focused comparison
| Observation | Likely cause | Action |
|---|---|---|
| Compile time falls and transfer remains small | Cache reuse is helping without becoming the new bottleneck | Keep the cache and continue monitoring |
| Compile time falls but downloads dominate | Cache endpoint is distant, artifacts are large, or reuse is incomplete | Test a closer endpoint, limit uploads, or change artifact scope |
| Uploads run after every job | Inputs are unstable or the upload policy is too broad | Review generated files, cache keys, and upload rules |
| Remote hits are rare across identical commits | Toolchain, architecture, paths, or authentication differ | Fix reproducibility before scaling the cache |
| Cache service is unavailable | Dependency on the cache is now part of job availability | Define a bypass path and measure the uncached fallback |
Does a low cache hit rate mean you should keep using it?
Not automatically. Keep it if the hits remove a meaningful compile stage and the transfer overhead stays below the saved work. Stop or narrow it if misses are caused by unstable inputs that you cannot correct, or if downloads consistently replace one bottleneck with another. Evaluate the end-to-end job, not the hit percentage alone.
Experience point: never compare a cached job with an uncached job from a different runner image and call the difference a cache result. That combines cache behavior with machine, toolchain, and queue changes.
SECTION 04Why a remote Mac queue changes the answer
A cache cannot create another execution slot. If the job waits for an available Mac, the cache is not involved yet. Self-hosted runner systems route jobs according to labels, availability, group rules, and matching requirements; the official self-hosted runner rules are a useful reference for separating routing from execution.
Use queue records to answer four questions:
- When did the workflow become eligible to run?
- When did a runner accept it?
- Was the selected Mac compatible with the labels and required tools?
- Did the job wait because all matching Macs were busy, offline, or misrouted?
How can you distinguish remote Mac CI queue time from slow compilation?
Put timestamps around runner allocation and the first build command. A long gap before the build starts is queue or routing time. A long interval after the build starts is execution time. A job that is fast after allocation but waits repeatedly needs capacity or routing work, not cache tuning.
Third step: split jobs by operational purpose
Do not send every task into one shared queue. A pull request build, a scheduled validation run, and a release archive have different urgency and environment requirements.
| Workload | Main constraint | Better control |
|---|---|---|
| Pull request compilation | Fast feedback and repeatable inputs | Cache eligible compilation and use a responsive build pool |
| Scheduled full validation | Broad coverage and predictable capacity | Reserve capacity or schedule around known demand |
| Simulator and UI testing | Graphics session, runtime, and test execution | Use a test pool with the required interactive environment |
| Release archive | Signing identity, provisioning, and artifact integrity | Isolate a controlled release pool |
| Recovery or retry jobs | Availability during incidents | Maintain a bypass route without depending on one cache service |
If queue time is the dominant term, add or split remote Mac nodes. If jobs wait because labels are too restrictive, correct routing before purchasing capacity. If release work monopolizes the same machines used for pull requests, isolation can improve feedback without changing the compiler.
SECTION 05Can the cache accelerate UI tests and signing?
It can reduce eligible compilation before those stages, but it cannot replace the stages themselves. A UI test still needs the app to launch in a Simulator or on a physical device, and signing still needs the correct certificates, provisioning assets, keychain access, and archive conditions. Apple’s Simulator and physical device testing documentation explains why test execution remains tied to the selected runtime and device environment.
Apple’s archive troubleshooting note also shows why archive failures often belong to signing, configuration, or packaging rather than compilation. A cache hit does not validate those conditions.
What should you do when UI tests or signing remain slow after cache hits?
Keep the cache if it shortens the compile portion, then measure the test and release pools separately. Do not classify an unchanged UI test duration as cache failure. If a graphical session, Simulator runtime, device, or credential environment is shared and occupied, create a compatible pool or route those jobs to additional remote Mac capacity.
The practical pros and cons are different by stage:
Tuist Xcode Cache advantages
- Reuses eligible compilation products across suitable jobs.
- Can reduce repeated compiler work without changing the project’s developer workflow.
- Helps most when the same inputs recur and the cache endpoint is reachable.
- Can complement additional runners when each runner still repeats the same compile work.
Tuist Xcode Cache limitations
- Does not remove runner queue time.
- Does not automatically accelerate scripts, UI tests, archives, or signing.
- Depends on stable inputs, authentication, compatible toolchains, and network transfer.
- Can make diagnosis harder if local hits, remote hits, and misses are not reported separately.
Additional remote Mac advantages
- Adds execution capacity when matching runners are occupied.
- Can isolate build, test, and release workloads.
- Provides a real macOS environment for Xcode, Simulator, credentials, and release tooling.
- Gives you a fallback path when cache access is unavailable.
Additional remote Mac limitations
- Adds provisioning, routing, monitoring, and environment maintenance work.
- Does not reduce repeated compilation by itself.
- Can waste budget if the real bottleneck is an unstable project or oversized cache transfer.
- Requires acceptance testing for restart recovery, network access, graphical sessions, and signing.
SECTION 06Use this three-way decision test
Run the same commit through comparable environments. Keep the toolchain, target, architecture, build configuration, and task scope fixed. Record queue, compile, transfer, test, archive, and effective delivery time as separate fields.
| Trial path | What it answers | Stop condition |
|---|---|---|
| Existing runner without cache | Baseline compile, queue, and non-compile stages | Stop when the baseline has enough repeated runs to expose the dominant stage |
| Existing runner with cache | Whether reuse reduces end-to-end time | Stop if inputs are not reproducible or transfer cancels the saved compile work |
| Additional or split remote Mac path | Whether capacity or workload isolation removes queue and environment delays | Stop if new capacity remains idle while compilation remains the dominant cost |
Use the following acceptance checklist before choosing a long-term path:
- [ ] Select one representative commit and keep it fixed for the comparison.
- [ ] Record runner allocation time separately from the first Xcode command.
- [ ] Save Xcode Build Timing Summary output for each comparable run.
- [ ] Label every cache result as local hit, remote hit, miss, or unavailable.
- [ ] Record cache upload and download time as separate pipeline stages.
- [ ] Confirm that Xcode version, SDK, architecture, scheme, and build settings match.
- [ ] Run the same UI test scope without treating test duration as compile duration.
- [ ] Run an archive and signing path separately from pull request compilation.
- [ ] Check whether release jobs occupy runners needed by pull requests.
- [ ] Define a bypass route for cache outage or authentication failure.
- [ ] Set a rollback owner and a date for reviewing the evidence.
- [ ] Compare effective delivery time, not only compiler time or cache hits.
The decision table
| Evidence from the trial | Choose | Why |
|---|---|---|
| Repeated compilation dominates; remote hits are reproducible; queue is acceptable | Keep and refine Tuist Xcode Cache | The primary waste is repeat compiler work |
| Queue wait dominates; compilation is a smaller share; matching Macs are busy | Add or split remote Mac nodes | Cache cannot execute a waiting job |
| Compilation and queue are both substantial | Run cache and capacity changes together | One change addresses compiler work; the other addresses availability |
| Cache inputs change across otherwise identical jobs | Fix project and environment reproducibility first | Scaling an unstable cache will not create reliable reuse |
| UI tests or signing dominate after compilation | Isolate test and release pools | The remaining work needs the correct execution environment |
| Transfer dominates after cache hits | Narrow uploads, improve locality, or bypass the cache for that path | The optimization moved the bottleneck to the network |
A useful stopping rule is simple: stop expanding the cache when the remaining job time is mostly queue, testing, signing, or transfer. Stop adding runners when they are available but the compiler repeats the same work. Revisit the decision after any Xcode, SDK, architecture, cache protocol, or runner-routing change.
SECTION 07Where a remote Mac fits after the evidence
If the test shows that your current Windows or Linux workstation can submit jobs but cannot provide the required macOS toolchain, a remote Mac is a more direct operational path than maintaining a fragile local workaround. The trade-off is real: remote access depends on network quality, and interactive debugging over VNC is not identical to sitting at the keyboard. However, a managed real Mac can provide a persistent environment for Xcode builds, Simulator work, and Mac CI while your primary workstation remains unchanged.
For a short validation window, MACNOX remote Mac access can be evaluated against your actual repository, runner labels, network route, and signing workflow. Check the failure cases before committing: disconnect and reconnect behavior, restart recovery, SSH access, graphical session availability, cache endpoint reachability, and whether credentials remain within your approved operating model.
If your current approach is a Linux cloud server, a local Windows machine, or a single Mac mini, its weaknesses are usually specific rather than theoretical: Linux cannot provide the required native Xcode environment, a local workstation is not continuously available to CI, and one physical Mac becomes a single queue and failure domain. Renting a remote Mac can be the better fit when you need temporary capacity, a second build lane, or a persistent macOS environment without buying another machine. It is less suitable for stable, heavy workloads that run continuously for a long period, or for workflows that require direct physical interfaces unavailable through remote access.
Review MACNOX pricing and access options only after the workload evidence identifies what capacity you actually need. If queue time, Simulator execution, or release work is the main cost, the next step is to validate remote Mac nodes and split the pools. If repeated compilation is the main cost, keep the cache experiment in place instead of treating more hardware as the automatic answer.