Short answer: You can change Swift logic, async code and SwiftUI views in a shipped native iOS app without a new App Store submission by delivering interpreted code — Patch compiles the changed Swift to WebAssembly and the on-device PatchSDK runs it in WasmKit. Apple permits this under DPLA §3.3.1(B) as long as the app's primary purpose does not change.

What does "without App Store review" actually mean?

"Without App Store review" covers six different mechanisms, and only two of them change code. Remote config changes values the app already reads — a threshold, an endpoint, a copy string. Feature flags switch between code paths that both already shipped. Server-driven UI sends a layout description the app renders with components already in the binary. A web view replaces a screen with a website. Interpreted code — the subject of this page — downloads new logic that an interpreter inside the signed binary evaluates. Only a new binary goes through App Review. Patch sits in the interpreted-code category: the Swift you edit becomes WebAssembly, and the signed binary Apple reviewed is never modified. The difference between flags and interpreted code is covered in feature flags versus OTA updates.

Which update paths does Apple allow?

Apple bans downloaded executable code and permits interpreted code under three conditions, at section 3.3.1(B) of the contract every Apple Developer Program member signed:

"Except as set forth in the next paragraph, an Application may not download or install executable code. Interpreted code may be downloaded to an Application but only so long as such code: (a) does not change the primary purpose of the Application by providing features or functionality that are inconsistent with the intended and advertised purpose of the Application (b) does not bypass signing, sandbox, or other security features of the OS; and (c) for Applications distributed on the App Store, does not create a store or storefront for other Applications."

— Apple Developer Program License Agreement, §3.3.1(B) "Executable Code", retrieved 22 August 2026 from the Apple Developer agreements page. The clause was numbered §3.3.2 before the agreement was reorganised.

A WebAssembly patch is interpreted code under that reading: WasmKit evaluates it instruction by instruction and downloads no machine code. Conditions (a) and (c) bind the developer, not the tool.

What does App Review Guideline 2.5.2 say?

App Review Guideline 2.5.2 is the reviewer-facing rule, and read alone it sounds like a flat prohibition:

"Apps should be self-contained in their bundles, and may not read or write data outside the designated container area, nor may they download, install, or execute code which introduces or changes features or functionality of the app, including other apps. Educational apps designed to teach, develop, or allow students to test executable code may, in limited circumstances, download code provided that such code is not used for other purposes. Such apps must make the source code provided by the app completely viewable and editable by the user."

App Review Guideline 2.5.2, Software Requirements, retrieved 22 August 2026.

Guideline 2.5.2 and DPLA §3.3.1(B) are read together: the guideline states the default, the agreement states the carve-out CodePush, Expo EAS Update and Shorebird operate under. Apple drew the enforcement line in March 2017 against SDKs that changed an app's behaviour after review, an episode covered in what Apple allows for OTA updates and in the Patch docs on Apple compliance.

What can an OTA patch change in a Swift app?

An OTA patch can change Swift logic, async/await code and SwiftUI view bodies — structure, state and interaction. The Patch build engine sorts every function into one of four buckets: pure logic compiles to WebAssembly; code that touches a bridged host API (networking, UserDefaults, keychain, notifications and 47 more) runs over the air and calls the native shell for the host part; mixed functions have their safe half lifted; and anything touching a must-stay-native API stays in the App Store binary. Code that calls OS frameworks directly — low-level UIKit rendering, AVFoundation, CoreLocation, HealthKit, Core Data — is never patched. Neither is a framework or entitlement your signed binary does not already contain. The full map is in the docs on what Patch can and cannot update.

How much of a real SwiftUI app ships over the air?

Across 24 measured apps — ours plus public repositories including firefox-ios, wikipedia-ios, IceCubesApp, Pulse and NetNewsWire — 74.6% of SwiftUI view bodies (1,024 of 1,372) compile to WebAssembly and ship over the air, and 82.5% of view elements (21,034 of 25,503) do. Per-app results range from 45% to 98%, so the spread matters more than the average. Those figures are generated from a committed census artifact by the corpus tool in the Patch repository, not typed by hand, and the named apps are public repositories measured at pinned commits. A view that cannot lower is not broken: it renders natively from the signed binary. The per-app table lives in the coverage documentation.

How does Swift become WebAssembly?

Patch compiles the changed Swift with the swift.org toolchain and the WebAssembly Swift SDK, which Xcode's own toolchain cannot do. The engine first partitions the project into a WebAssembly module and a native shell, then compiles the module at the lightest packaging tier that works: Embedded Swift at tens of kilobytes when Foundation values can be satisfied by host bridges, a stdlib-only tier at roughly 1.1 MB, and full Foundation at roughly 11.7 MB when the code genuinely needs in-module Foundation. After the first module, each update ships as a compressed binary diff against the version already on the device. The compiler-level detail is in compiling Swift to WebAssembly and the pipeline in the docs on how Patch works.

How do I set it up?

Setup is two machine-level commands and one per-app command. Every step is re-runnable and skips work already done.

  1. Install the CLI. Homebrew fetches the patchcli binary from the Patch tap.

    bash
    brew install patch-release/tap/patchcli
  2. Install the Swift-to-WebAssembly toolchain once per machine. This downloads and checksum-verifies the pinned swift.org toolchain and the WebAssembly SDK.

    bash
    patchcli setup
  3. Set the app up. patchcli init detects the Xcode project, registers the app in the browser, adds the PatchSDK Swift package, proposes the startup-code diff, and makes the SwiftUI views patchable.

    bash
    patchcli init
  4. Edit Swift as usual. No annotations, no wrapper views, no special dialect.

  5. Release. patchcli release builds the module and uploads it in one shot, gated on the fingerprint check.

    bash
    patchcli release --message "Fix the checkout total"

How does the device pick up the update?

The device picks the update up on its next update check, which the SDK runs at launch. Patch.shared.start() first activates the best module already cached on disk, so the app runs its current over-the-air code immediately and offline, then checks the backend in the background and applies anything new. The check reports the device's channel, its app version, its OS version and the native-shell fingerprint of the installed build; the backend answers with the module for that channel if the device falls inside the rollout percentage and the fingerprint matches. Any failure — missing module, corrupt download, failed activation — leaves the previously active module in place and falls back to the native code baked into the signed binary. The lifecycle is documented in the PatchSDK reference.

How do I roll a patch back?

One command rolls a release back on a channel, and the previous module re-activates.

bash
patchcli rollback --channel production

The rollback propagates to devices on their next update check. A device that already downloaded the rolled-back release and has no active replacement is told to revert on that check: the SDK deactivates the patch locally and the app returns to the code in the signed binary. Rollback is channel-scoped, and --to <version> rolls back to a specific version, unwinding everything newer first. The mechanics and the runbook are in how to roll back an iOS release instantly and in the docs on staged rollouts and rollback.

How do I stage a patch to 10% of devices first?

Pass a rollout percentage on the release, then widen that same release in place.

bash
patchcli release --rollout 10 --message "New pricing engine"

The backend buckets each device by hashing its stable device id together with the release into a value from 0 to 99, and serves the release when the bucket falls below the rollout percentage. Bucketing is deterministic, so a device that is in the rollout stays in as you raise the percentage. Widen from the console or the API rather than re-running patchcli release, which publishes a second module and re-buckets everyone. Channels are the other axis: a build subscribes to production, staging or any string, and only receives releases pushed to it — see the channels documentation.

What happens if the native code changed since the last App Store build?

The release is refused, on purpose. Patch hashes the app's native shell — native Swift files, Info.plist, entitlements, linked frameworks, deployment target, SDK version and compiler version — into a fingerprint, and records it with every release. A device reports the fingerprint of the build it is running, and the backend serves a patch only on a match. Editing a view body that already ships over the air does not move the fingerprint, because patchable bodies are subtracted from the hash per function. Upgrading Xcode does move it. Diagnose and re-register with these two commands, then read the compatibility fingerprint page.

bash
patchcli fingerprint diff --explain
patchcli fingerprint register

Does this work for UIKit?

Partly, and the honest answer is that the SwiftUI path is far wider. SwiftUI view bodies are made patchable automatically by patchcli prepare, which runs inside every build, push and release. For UIKit, the engine lowers the declarative construction of table and collection-view cells and of programmatic view controllers, and the PatchUIKit target in the SDK rebuilds that tree on device. Swift business logic behind a UIKit screen patches exactly as it does behind a SwiftUI screen, because the partitioning engine does not care which framework calls the function. Low-level UIKit rendering stays native in both cases. The published coverage census measures SwiftUI only, so treat UIKit view coverage as unmeasured rather than as a number.

What does an OTA update layer cost?

Patch has three tiers. Hobby is free and covers 100 devices. Startup is $59 per month. Enterprise is priced on request. The CLI engine is Apache-2.0 licensed and the SDK is MIT, published at github.com/patch-release/patch-swift and installed with Swift Package Manager, so the client side of the system is readable and forkable. Current tiers and limits are on the pricing page. The cost worth comparing against is not another vendor's line item but the engineering hours a team spends on a hotfix cycle that ends in a resubmission — the arithmetic depends on your release cadence, so run it with your own numbers.

When should you still ship a binary?

Ship a binary whenever the change is one an OTA patch structurally cannot make. Four cases cover almost all of them:

  • A new capability that needs an entitlement or a privacy-usage string the signed Info.plist does not declare.

  • A new framework or third-party SDK the binary does not already link.

  • A change to native code the engine classified as native — which moves the fingerprint and requires a new build anyway.

  • A feature large enough that shipping it over the air would change what the app is, which is exactly what DPLA §3.3.1(B) condition (a) forbids.

An OTA layer is for the work between releases: bug fixes, copy, layout, business rules, flow changes. The regular App Store cadence still carries the product forward.

How is this different from CodePush, Expo EAS Update and Shorebird?

Those three tools patch a different artifact. Microsoft's CodePush replaced React Native JavaScript bundles, and the hosted App Center service that ran it retired on 31 March 2025; Microsoft now points teams at a self-hosted server. Expo EAS Update is the React Native successor, and describes itself as serving updates for a project's "own non-native pieces (such as JS, styling, and images)". Shorebird patches Dart for Flutter apps. None of them patches Swift. Patch is the native-Swift equivalent: the artifact is WebAssembly compiled from your Swift rather than a JavaScript or Dart bundle. Side-by-side detail is in the OTA update tools comparison, the CodePush alternative, the Expo EAS Update alternative and the Shorebird alternative pages.

Frequently asked questions

Is OTA code updating allowed by Apple?

Yes, conditionally. Apple's Developer Program License Agreement §3.3.1(B) permits interpreted code to be downloaded to an application provided it does not change the app's primary purpose, does not bypass signing or sandbox protections, and does not create a storefront for other applications. Downloading executable code is banned outright. Compliance with those three conditions is the developer's responsibility, not the tool's.

How long does App Store review take if I do need it?

Apple states on its App Review page: "On average, 90% of submissions are reviewed in less than 24 hours." That is an average, not a guarantee, and it does not include the time you spend building, testing and resubmitting, or the time users take to install. Expedited review exists for critical bug fixes and event-tied launches.

Can CodePush or Expo do this for a Swift app?

No. CodePush replaced React Native JavaScript bundles and its hosted App Center service retired on 31 March 2025; Expo EAS Update serves updates for a React Native project's non-native JavaScript, styling and images. Neither compiles or ships Swift. A native Swift app has no JavaScript bundle for them to replace.

Is there a Shorebird for Swift?

Shorebird is the Flutter equivalent and patches Dart code; its FAQ states that Shorebird "does not support changing native code (e.g. Java/Kotlin on Android or Objective-C/Swift on iOS)". Patch fills the same role for native Swift by compiling the changed Swift to WebAssembly and running it on device in WasmKit.

Does WebAssembly count as interpreted code under the DPLA?

It depends on how the WebAssembly runs, and Apple has published no ruling naming WebAssembly. A pure interpreter such as WasmKit evaluates the module without generating machine code or writable-executable memory, which is the same shape as JavaScript in JavaScriptCore or Hermes bytecode in Hermes. Apple, not any vendor, decides how the clause applies.