How would you plan the migration of a large Objective-C app to Swift?
Predict first
What is the best first step before converting a large, untested Objective-C class?
// A: rewrite it in Swift immediately
// B: add characterisation tests, then annotate its header
// C: mark it @objcMembers
- A
- B
- C
Mental model. Replacing the planks of a ship while it sails. You never take up the whole deck, and you check each plank holds before moving to the next.
Do not rewrite. Migrate incrementally, behind stable seams, and keep the app shippable every week. First, make the Objective-C pleasant for Swift without converting anything: add nullability and lightweight generics to headers, NS_SWIFT_NAME and NS_ENUM, so new Swift code feels native. Add characterisation tests around code you are about to move, since a migration is exactly when behaviour drifts. Set the rule that all new code is Swift, and let the Objective-C shrink as modules are touched for other reasons.
Then move bottom-up: leaf utilities, models and networking first (few dependents, easy to test), view controllers and the app delegate later. Define the boundary with small protocols or facades so each side hides behind them, and avoid spreading @objcMembers. Track progress with numbers (share of Swift lines, crash-free rate, build time) and agree what “done” means, because the last 5% (swizzled legacy, +load hooks, C++ interop) is the costliest. Staff-level work here is mostly process: ownership, review rules, and deciding what stays Objective-C on purpose.
Tests pin down current behaviour, and nullability annotations make the seam typed. Only then is moving the code safe and reviewable.
Common traps
- A big-bang rewrite. It freezes feature work, loses hard-won behaviour, and rarely lands.
- Converting UI first. It has the most dependencies and the least test coverage. Start at the leaves.
They might follow up with
- How do you decide what not to migrate? Stable, rarely changed code with no pain (and no owner’s interest) can remain Objective-C indefinitely.
- What do you measure? Crash-free sessions, build and launch time, binary size, and the share of new code that is Swift.
Related questions
- 4.5.1 How do nullability annotations change what Swift sees, and what happens when they lie?
- 4.5.3 How do you make an Objective-C API feel native in Swift?
- 4.8.2 What does mixing Swift and Objective-C cost, and how do you keep the cost down?
← What does mixing Swift and Objective-C cost, and how do you keep the…