This article is for indie developers who have an iOS app they built years ago, left untouched, and now want to get it running again on the latest Xcode / Swift and back on the store. I modernized my personal bookshelf management app "My Books" in a 6-day pair programming session with Claude Code, targeting Xcode 26 / Swift 6 / iOS 26, and shipped it as v3.0.0 through 18 TestFlight builds to the App Store on 2026-05-02. I'll cover everything — including the traps I hit on real hardware after compilation was already succeeding.
What this article covers
- The process of migrating the frozen Swift / CocoaPods / Realm / third-party SDK stack from a 2016-era app to the current stack and shipping it to the App Store on 2026-05-02 — My Books (bookshelf management app), first released 2016, untouched for 7 years
- Editing in WSL2 with Claude Code, SSHing the build to a separate Mac, automated TestFlight shipping in a single command
- The real-device TestFlight crash gauntlet after compilation passed (DGCharts vtable, Rakuten API shutdown, Google Books quota exhaustion, etc.)
- How AI pair programming split "what the human needs to do" from "what to hand to the AI"
By the numbers
| Item | Value |
|---|---|
| Working period | 2026-04-25 – 2026-05-01 (6 days) |
| Commits | ~60 |
| TestFlight builds | 18 |
| Tests added | 20 (AppUtilTests / BookTests / ItemEnumsTests / ViewUtilTests) |
| Release version | v3.0.0 / Build 18 |
| App Store release | 2026-05-02 |
| Target environment | Xcode 26 / Swift 6 / iOS 17+ / old master branch frozen |
Overall flow
Three broad phases. Of the 6 days, 5 were modernization, half a day was shipping pipeline setup, and the rest was handling TestFlight issues and the App Store review submission. Build 18 passed review without additional questions and was published on 2026-05-02.
CocoaPods → SPM, Realm @Persisted, async/await, ViewController split, secrets management, test coverage
WSL → SSH → Mac → archive → IPA → altool → TestFlight
Builds 5→18, stamped out real-device crashes and API issues, Build 18 submitted → v3.0.0 published 2026-05-02
① What modernization involved
Plan → delete → API migration → Realm overhaul → ViewController split → secrets cleanup → test coverage, in that order.
- CocoaPods → SPM (Realm 20.0.4 / Firebase iOS SDK 11.15.0 / Google Mobile Ads 11.13.0 / DGCharts 5.1.0)
- Deleted all Amazon PA-API code: old
RWMAmazonProductAdvertisingManager/HMAC.m/ Bridging Header / BarcodeCacher companion app — all gone - Eliminated all deprecated APIs:
arc4random_uniform/statusBarOrientation/UILocalNotification/kCLLocationAccuracyKilometer/AVCaptureConnection.videoOrientationand more — over 30 locations total - async/await conversion: rewrote
ProductSearchto parallelwithTaskGroup. Unified Google Books / Rakuten / OpenBD providers toasync throws. Removed all delegate protocols - Realm modernization:
@objc dynamic→@Persisted. Dropped old migrations in favor ofdeleteRealmIfMigrationNeeded = true+ a "Data was reset" alert to the user - ViewController split: 1,135 lines → 471 lines, with four extensions (Camera / ProductSearch / Library / Speech) carrying separate responsibilities
- Secrets management: committed
Config/Secrets.template.xcconfig, gitignored the actualSecrets.xcconfig. Info.plist values expand via$(VAR)
② TestFlight shipping pipeline
I wanted code editing in WSL2 → GitHub push → SSH to a separate Mac → archive → IPA → TestFlight upload as a single command. The final form is three scripts:
scripts/setup_build_keychain.sh # run once — duplicates cert into dedicated build keychain
scripts/archive.sh # runs xcodebuild archive non-interactively in Release config
scripts/upload.sh # reuses archive, exports IPA, uploads via xcrun altool
altool auth uses an App Store Connect API Key (.p8). I also use fastlane for test and build validation, but the actual shipping uses plain xcodebuild + altool.
③ TestFlight crash gauntlet
This ate the most time — Build 5 to Build 18, a day and a half. The gap between compilation succeeding and actually running on a real iOS 26 device was larger than expected.
Build 18 went to App Store review and passed without any additional questions from Apple. v3.0.0 published to the App Store on 2026-05-02. The version and build number were kept as-is from the final TestFlight build.
5 memorable hits that left an impression
1. Xcode 26's build sandbox
A dead scripts/update_storyboard_strings.sh (containing only a comment) and its Run Script Phase were still in the project. Xcode 26's build sandbox escalates even an empty Run Script Phase to a build error if path resolution fails. A script containing only a comment was enough for the sandbox to flag it — the entire Run Script Phase had to be deleted.
2. Rakuten Web Services shutting down in 13 days
While tracking down missing book cover images, I discovered that the Rakuten Books API was in the middle of fully shutting down the old app.rakuten.co.jp endpoint on 2026-05-14 and migrating to the new openapi.rakuten.co.jp. 13 days to go.
- Required parameters expanded to
applicationId+accessKey - Application type is now either Web Application (allowed website) or Backend Service (allowed IP) — two choices
- Mobile apps must use Web Application. The
Originheader is mandatory; without it you get403 REQUEST_CONTEXT_BODY_HTTP_REFERRER_MISSING
Re-registered as a Web Application type under the new domain → stored the new ApplicationId / AccessKey / allowed domain in Secrets.xcconfig → rewrote ProductSearchByRakuten.swift to attach Origin / Referer headers on every request. If you have an app using Rakuten Web Services, migrate as soon as possible.
3. Google Books anonymous shared quota exhausted
Calling the Google Books API without a key hit 429 Quota Exceeded. The project_number in the error JSON wasn't mine. Re-reading the docs: unauthenticated calls share a single anonymous project's 20M/day quota across every anonymous caller in the world. Constantly exhausted. Fix: issue an API key from my own GCP project and send it via ?key= query parameter.
4. DGCharts vtable consumed 4 builds — replaced with custom CoreGraphics implementation
Builds 5–8 crashed with the same symptom. The PieChartView.data = ... setter inside ShelfViewController.setChart() hit EXC_BAD_INSTRUCTION (brk #1). Running atos on the .ips crash log revealed 647 symbols aliased to the same stub inside DGCharts' DGChartsDynamic.framework. Suspected dead-strip gone wrong under Xcode 26 + Swift 6 + SPM.
Neither switching static→dynamic nor routing through objc_msgSend fixed it. I ultimately replaced the chart feature (a 3-slice donut with center text) with a custom BookStatusPieView: UIView (~60 lines of CoreGraphics). Swap the customClass in the Storyboard and done.
5. SSH-based codesign and the TestFlight shipping pipeline
The problem of codesign failing to access the login keychain when SSHing into the Mac to run xcodebuild archive (the login keychain stays inaccessible to codesign even when unlocked over SSH), and the solution of using a dedicated build keychain with relaxed ACL to automate the pipeline in a single command — that grew long enough to split into its own article. The full procedure for WSL2 → SSH → Mac → archive → IPA → altool in three scripts is in "Build & Ship iOS Apps to TestFlight via SSH from Your Smartphone".
Division of labor in AI pair programming
After 6 days of running this, where humans and AI each excel became clear.
| Responsible party | Tasks |
|---|---|
| Claude Code |
Symbolizing crash logs (.ips) with atos / API triage with curl / code fixes /
archive and upload over SSH / keeping docs and memory in sync / generating commit messages
|
| Human | Physical device interaction / App Store Connect agreements and submissions / GCP and Rakuten Developers browser sessions / the physical senses (volume levels, speaker behavior, banner rendering verification) |
Not forcing AI onto tasks it's bad at translated directly into efficiency gains. Conversely, crash log symbolization and API triage via curl are dramatically faster with AI.
What I learned
- Compilation passing ≠ working: Xcode can only catch so much of 7 years' worth of API changes statically. The majority of issues only appeared after pushing to real-device TestFlight (DGCharts vtable, phantom Storyboard outlets, ATS, ATT, AVAudioSession)
- Third-party APIs evolve independently: Google Books anonymous quota exhaustion, Rakuten's old API 13 days from shutdown, AdMob SDK 11+ requiring explicit
adSize— all changes that happened independently of the app's own code - An automated delivery pipeline is a prerequisite: without the one-command WSL → SSH → Mac → TestFlight pipeline, running Builds 1 through 18 in a single day would have been impossible
- Xcode 26 traps:
ENABLE_DEBUG_DYLIB/ sandbox / phantom schemes / SPM Embed Frameworks — you'll hit these both while setting up the test target and while building the shipping pipeline - You're done when a full day on real hardware produces nothing: code changes are the front half; TestFlight crash response takes roughly the same amount of time
Note: The work in this article was done on Xcode 26 / Swift 6 / iOS 17–26 as of May 2026. Third-party API changes or SDK version differences may mean some steps won't reproduce exactly. If you notice anything, let me know in the comments.
Wrap-up
Even a personal iOS app that sat untouched for 7 years can make it to the App Store as v3.0.0 in 6 days when you combine the latest Xcode with Claude Code. That said, "compilation passes" and "runs on real hardware" are two different things — the crash response after pushing to TestFlight takes more time than you'd expect. Third-party API drift and Xcode 26's build sandbox act on their own schedule, regardless of what you changed in your own code. Get the delivery pipeline automated early and you can move fast from there.
App Store: My Books (bookshelf management app) (v3.0.0, published 2026-05-02). If this article was helpful, I'd love it if you shared it on X (Twitter).
App by the author of this blog
I made an iOS reading management app called My Bookstore. Simple bookshelf management — give it a try.
Related articles
- Generating AI Blog Images with a Local GPU [SDXL + IP-Adapter + img2img]
- Build & Ship iOS Apps to TestFlight via SSH from Your Smartphone [SSH + build keychain]
References
- Xcode Build System (sandbox behavior)
- Realm Swift Documentation
- Rakuten Web Services API Reference (new domain)
- Google Books API
- OpenBD
Note: This article is part of an automated blog update experiment using Claude Code.
No comments:
Post a Comment