fix(ios): configure the Swift AppDelegate on Expo SDK 53+ - #32
Merged
Conversation
`replaceIfNotFound` and `addBelowAnchorIfNotFound` both ended up calling `String.replace` with a needle that wasn't there, which returns the input untouched, so a mod that found none of its anchors reported success and wrote nothing. That is how #30 sat unnoticed across five SDK releases while the Android side, which throws, got reported the week SDK 57 landed. `addBelowAnchorIfNotFound` did have a throw, but behind the wrong check: it ran the replace first and only tested the anchor when the insertion was already present, so the one case worth catching fell through. Both now check for the change being already applied, then throw on a missing anchor, then edit. Android already guarded every call with its own `includes` check, so nothing changes there. Claude-Session: https://claude.ai/code/session_01QsVEsfkynXpJWK4t33exG9
The mod looked for `#import "AppDelegate.h"` and the Objective-C `[[NSBundle mainBundle] URLForResource:@"main" ...]` line. SDK 53 rewrote the template in Swift, so from 53 through 57 neither anchor is in the file and the app kept booting from the bundle baked into the IPA. Swift gets the release arm of `ReactNativeDelegate.bundleURL()` swapped for `CodePush.bundleURL()`. The `#if DEBUG` arm stays on Metro. That block is byte-identical across 53 to 57, and the pattern tolerates the call being reflowed. Swift cannot import CodePush directly, since it is an Objective-C pod with no module map, so `#import <CodePush/CodePush.h>` goes into the bridging header instead. Its path comes from the Xcode project's `SWIFT_OBJC_BRIDGING_HEADER` rather than from guessing at the project name. Objective-C app delegates import CodePush themselves and skip it. SDK 50 through 52 keep the Objective-C path unchanged. Closes #30 Claude-Session: https://claude.ai/code/session_01QsVEsfkynXpJWK4t33exG9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #30, which has the diagnosis.
Silent no-ops
This is why the Swift breakage below lasted five SDK releases, so it gets its own commit.
replaceIfNotFoundandaddBelowAnchorIfNotFoundboth ended up callingString.replacewith a needle that wasn't in the file, which hands back the input untouched. A mod that matched none of its anchors wrote nothing and reported success.addBelowAnchorIfNotFounddid have a throw, sitting behind the wrong check: it ran the replace first and only tested the anchor when the insertion was already present, so the one case worth catching fell through it. Both helpers now check for the change already being applied, then throw, then edit. Android guarded every call site with its ownincludestest, so nothing moves there.Swift
SDK 53 is where the template changed. The issue says 52; I corrected it there too. I pulled
expo-template-bare-minimumfor every release from 50 to 57:AppDelegate.mmthrough 52,AppDelegate.swiftfrom 53 on. TheReactNativeDelegate.bundleURL()block is byte-identical across 53 to 57, so one anchor covers all five.The release arm of that method becomes
CodePush.bundleURL(). The#if DEBUGarm keeps loading from Metro, which CodePush has nothing to serve. Matching is a pattern, so a reflowed call still lands.Swift can't see CodePush directly. It's an Objective-C pod with no module map, so the import goes in the bridging header, and the path comes from the Xcode project's
SWIFT_OBJC_BRIDGING_HEADER. Objective-C app delegates import CodePush themselves and skip that step.Proof
Blank
create-expo-apponexpo@57.0.8, plugin inapp.json,npx expo prebuild --platform ios --clean.Before, v1.0.12 from npm. Prebuild says it finished,
grep -rl CodePush ios/finds onlyInfo.plist:After, this branch packed into the same app. Running prebuild twice without
--cleanleaves one of each:The prebuilt app compiles.
pod install, then a Release simulator build with signing off, which is the configuration the fix touches. 271 CodePush symbols land in the app binary. Deleting only the bridging-header import from that same tree fails atAppDelegate.swift:66withcannot find 'CodePush' in scope:expo@50.0.21on the same branch, Objective-C output unchanged and the bridging header left alone:Local checks:
Tests run against the SDK 50, 52, 53 and 57 templates verbatim, kept in
src/ios/__tests__/templates.ts, and cover both throw paths on each language. 66 tests, up from 31.Out of scope
The SDK 50 app got no Xcode build, only prebuild. Its
AppDelegate.mmand bridging header come out byte-identical to what published v1.0.12 writes, which I diffed.The simulator build proves the generated code compiles and links. It doesn't prove CodePush serves an update at runtime, which needs a server and a signed release build, and
react-native-code-push@9.0.1hasn't shipped since the repo was archived.https://claude.ai/code/session_01QsVEsfkynXpJWK4t33exG9