Why big apps aren’t moving to Swift (Yet)
I strongly believe Swift is the future of iOS development. It’s only a matter of when, and the blocker is the breakneck speed it evolves. For smaller apps, Swift is good enough. For big apps, it’s at least a year away.
I’m using “big” as shorthand for a few different categories. It can mean lines of code, number of developers, complexity, or low-tolerance for problems. A lot of them make up the App Store Top 100 Free Apps. In January, Ryan Olson analyzed the chart and found 89% contained zero Swift.
That doesn’t mean 11% of these apps have adopted it. One team I spoke to has begun experimenting with Swift, to monitor its production-readiness in the coming year. The default language for all their new work is still Objective-C.
If you’re working in a smaller app, stop reading. The benefits of Swift 3.0 probably outweigh the risks. If you’re curious about the challenges of large companies, large codebases, and complex dependencies, this post should explain why big projects are holding back.
Every major Xcode update changes the language and breaks code. Sometimes the code migrator works, and sometimes you need to fix it by hand.
This is manageable if you’re under 30,000 lines of code. I’d wager the most apps in the world out there fall into this category. At the other end of the spectrum is the Facebook app, with 18,000 classes. That’s not lines-of-code, that’s classes. Let’s say that’s a million lines of code.
If 2% of that codebase moved to Swift, that’s 20,000 lines of code, and it’s not going to be in one place. It’ll be spread out in disabled experiments or old refactored code paths you aren’t sure are still being used.
Even if the code migrator “just works,” the poor engineer responsible for migration will spend a week running blame to track down the original authors, to verify the updated code still does what it’s supposed to do.
Of course the company can’t halt development during migration. Every day, new code lands. Our poor engineer becomes Sisyphus, but instead of a boulder rolling down the hill, it’s merge conflicts.
The lesser known churn is Swift’s internal evolution — most important, the Application Binary Interface. An ABI defines the layout of data structures, how arguments are pushed onto the stack, how applications make system calls, and other nuts-and-bolts with no bearing on 99.99% of daily development.
Swift 3.0 was going to lock down the ABI, but the Swift team recently announced it won’t make the cut. What does this mean?
An ABI matters when two isolated modules of code need to talk to each other. For example, C can call Ruby code so long as it sets up a stack frame in a way the runtime expects.
Say you built a framework in Swift 2.2, and the app that consumes also runs Swift 2.2. Despite both apps being built independently, they can talk to each other, because they all talk in the same low-level structures.
Say you move your app to Swift 3.0. If the ABI changed so that arguments are placed in different spots in the stack frame, you’ll (hopefully) crash.
The only solution is to compile everything with the same version of Swift. Migrate your framework to Swift 3.0 at the same time as your main app.
Don’t Build an SDK in Swift
If you’re providing a pre-compiled SDK, an unstable ABI makes life miserable. When a new version of Swift drops, you need to immediately recompile and distribute the latest version to your users.
Say the latest version of your SDK is 2.0. Your customers may be on 1.0, which you still support, because 2.0 had breaking changes. Not only do you need to migrate the latest version of your SDK to the latest Swift, but you need to migrate your old SDK too.
Another issue that can affect your SDK’s adoption is the hit on an app’s download size. Because the Swift core libraries aren’t shared on iOS, they’re bundled within the app. In a pure Objective-C app, adding a Swift-powered SDK carries a five megabyte penalty.
Watch out for Complicated Dependencies
Companies with multiple apps often share internal frameworks. These might include infrastructure like the networking stack, or product-facing features like a login screen.
Because all frameworks need to run the same version of Swift, all your apps need to transition at the same time. A company like Facebook would need to migrate the main Facebook App, Messenger, Groups, and whatever else. This means coordinating dozens of teams and hundreds of developers.
Issues with Tooling
Even on frameworks compiled within the same workspace, LLDB has issues with mixed language frameworks. For example, you’ll get a crash if you set a breakpoint in your network stack (Framework A) taking your a model (Framework B) as an argument.
In very large codebases, using pre-compiled frameworks of your own code have a meaningful reduction in build times. Unfortunately, you’re left trading debugability for longer build times, or jumping through hoops toggling project targets on a file-by-file basis when it comes time to debug.
When mixing and matching Objective-C and Swift across several frameworks, you can make SourceKit crash as frequently as July 2014, with Swift 1.0. You lose syntax highlighting and code completion. This is a meaningful barrier to new Swift developers, or when working Cocoa’s infamously long-winded method names.
While this doesn’t strictly have to do with the ABI, ABI stability is a blocker.
Framework Assisted Migration
To wrap up ABI talk, let’s not just focus on negatives. ABI stability can mitigate the much larger roadblock, language churn.
A strategy I’ve seen among big apps is to decompose their app into frameworks even though the code isn’t used in any other apps. Frameworks enforce boundaries that make testing easier, and clarify code ownership.
With a stable ABI, you can keep each of these frameworks on an old version of Swift, and migrate them piecemeal. Each team can be responsible for migrating their own code.
This might sound like I’m trashing the Swift team’s work, but that’s absolutely not the case. The Swift team has prioritized evolving the language and ecosystem, instead of stability and dealing with edge cases. That’s a perfectly reasonable tradeoff.
Even if the platform were stable, big companies switch platforms at glacial speeds. It took Adobe a decade to move Photoshop from Carbon to Cocoa You’ll get better feedback from early adopters than large apps, and you won’t be tied down by multi-year commitments to stability.
It’s worth repeating: most of what I described probably doesn’t apply to you. You’re probably working on a much smaller codebase with much simpler dependencies. The advantages of Swift 3.0 likely outweigh the drawbacks.