Back to blog
iOS

From Objective-C to Swift: Practical Migration Lessons

March 15, 20248 min read

Why We Decided to Migrate

In 2017 I was building a live streaming platform at Savvycom for an international client. The app was entirely Objective-C and it was hurting: new hires struggled with the codebase, bugs were hard to track down, and the lack of type safety caused runtime crashes in production.

Swift had reached version 4 by then, so we decided to migrate module by module rather than rewrite.

The Strategy: Incremental, Not Big Bang

Rewriting everything at once is the mistake teams make. We took a different path:

  • New features in Swift only: Every new screen, service, or utility was Swift from day one.
  • Bridge layer first: Clean bridging headers, so Objective-C and Swift could coexist without friction.
  • Migrate by module: We prioritized modules with the most bugs or the most active development. Networking first, then the data models, then the UI components.
  • Keep shipping: Every sprint had both feature work and migration work.

Challenges We Didn't Expect

The Bridging Header Nightmare

Objective-C categories, macros, and C functions don't always translate cleanly. We lost entire days to bridged enums with different raw values and category methods that were invisible to Swift.

Lesson learned: Audit your Objective-C code for Swift compatibility first: variadic functions, complex macros, and C constructs that won't bridge.

Singleton Patterns

Our Objective-C code was full of singletons built on dispatch_once. Swift makes them trivial (static let shared), but during migration two versions of the same singleton could exist at once, one in each language.

We kept the Objective-C singleton as the source of truth and wrapped it with a Swift interface until the dependent code was migrated.

Core Animation and WebRTC

The app leaned on Core Animation for gift effects and WebRTC for live streaming. Both were tied to runtime features like method swizzling and KVO, so we left them in Objective-C and wrapped them with Swift protocols.

Lesson learned: If a module is stable, well tested, and rarely changed, wrapping it beats rewriting it.

What We Got Right

Protocol Oriented Design

We defined protocols first instead of converting Objective-C classes straight into Swift classes. That gave us clean interfaces, unit tests against protocols, and room to swap implementations without breaking existing code.

Automated Testing as a Safety Net

Before touching a module, we wrote integration tests for its public API and confirmed the migrated Swift version behaved the same as the Objective-C original.

Pair Programming During Migration

We paired developers who knew the Objective-C codebase with developers stronger in Swift. That transfer prevented both tribal knowledge loss and migration mistakes.

Results

After 4 months of incremental migration:

  • 60% of the codebase: was in Swift
  • Crash rate dropped by 35%: thanks to Swift's type safety
  • Onboarding time for new developers: went from 3 weeks to 1 week
  • Feature development velocity: increased noticeably

Key Takeaways

  • Never do a big bang rewrite.: Migrate incrementally, keep shipping.
  • Bridge, don't break.: Make Objective-C and Swift coexist cleanly before migrating.
  • Wrap stable modules.: Sometimes a Swift wrapper is enough.
  • Test before you touch.: Write integration tests for any module before migrating it.
  • Pair your team.: Migration is a knowledge transfer opportunity, not just a technical task.

If you're facing a similar migration, reach out. I'm happy to share the patterns and tools we used.

Read next

iOS Developer Switching to Flutter: What I Wish I Knew Sooner

After 6 years of native iOS development, I switched to Flutter. Here are the most surprising things.