Daily coverage of WWDC20.
A Swift by Sundell spin-off.

Setting up your environment for Apple’s betas

Published at 16:20 GMT, 21 Jun 2020
Written by: Gui Rambo

It’s that time of the year again — major releases of Apple’s operating systems will be announced tomorrow. For a good number of developers, that means installing the betas from day one, which in turn means living on those betas until at least mid-September. This article contains a collection of tips and tricks that I’ve learned over the years dealing with various OS betas, multiple Xcode versions, and the problems that those can cause.

Getting the betas

As soon as tomorrow’s keynote ends, I expect that Apple will be updating their developer downloads page with the latest betas. For iOS, iPadOS, tvOS and watchOS, they are distributed as profiles.

To install those profiles, either access the downloads page directly from the device on which you want to install a given beta, or AirDrop the profile from your Mac to that device. Profiles for watchOS are installed through the watch’s paired iPhone, and profiles for tvOS are installed through Apple Configurator. If Apple decides to also release a beta for HomePod, then that’s also installed through the iPhone that the HomePod is paired to.

For macOS, there’s no profile, but rather an installer package that configures your machine to request beta updates instead of the release ones. Sometimes that process also involves launching the Mac App Store to download the beta for the first time. Just run through the installer steps and you’re done.

Xcode betas are distributed as xip archives — which are like zip files, but slower to decompress — so installing them is just a matter of downloading such a file from Apple’s developer portal, and then decompressing and dragging the resulting app into your /Applications folder. Just be careful not to overwrite the release version of Xcode, since you’ll still need it (more on that later).

Avoiding common pitfalls

In an ideal world, every developer would have multiple devices so that they wouldn’t have to install beta OSs on their daily drivers. I’m very fortunate that I’ve been able to achieve just that. My setup this year will be to install the iOS beta on my secondary iPhone — an iPhone 11 — and the macOS beta on my secondary Mac — a lovely 13” “MacBook Escape”.

A lot of Apple devices in a grid, shot from above

What kind of devices that you have access to will vary a lot between different developers, but I highly suggest not installing the betas on production devices on day one. Maybe you have an older iPhone laying around you can use to test the iOS beta? If you do, use that one. If you don’t have more than one Mac, you can dual boot your current OS and the beta, so that you can easily switch between them as needed.

⚠️ Dual booting macOS Catalina and Big Sur will prevent you from performing Time Machine backups and system updates of your Catalina partition, so that is not recommended this year.

I can’t stress this enough: please have backups! Backup your Mac and your iOS devices before installing a beta, so that if something goes wrong, you can always restore them back to their previous state — without losing any of your precious data. Another suggestion is to not log into your primary iCloud account on betas, since that could cause issues with accessing your data on other devices.

Dealing with multiple Xcode versions

This is something that developers tend to forget, but you can’t submit any apps to the App Store using beta versions of Xcode. You can upload your betas to TestFlight, but you can’t release a version to the App Store for customers to buy or download. This means that you’ll have to keep at least two versions of Xcode installed: the latest release version and the latest beta.

I personally even go beyond that, and keep both the latest beta and the previous one, so that if there’s a bug in the latest beta that prevents me from getting my work done, then I can keep using the previous one until the next beta is released. To keep things organized, I always rename Xcode.app by appending the version and beta number, such as Xcode12-b1.app, Xcode12-b2.app, and so on.

After you install a new version of Xcode, always make sure to launch it at least once so that it has a chance to “install supporting files”. To avoid problems, never keep two different versions of Xcode running at the same time, the same goes for the Simulator.

If you rely on command line tools that call into Xcode’s own binaries to do some work, then make sure that you have the correct version selected as your system’s Xcode version. That can be managed by using the xcode-select command. Run xcode-select -p to print the path to the current version, and run sudo xcode-select -s /Applications/Your-version-of-Xcode.app to change what version to use.

Adopting new APIs without breaking your project

Now that you have your environment all set up and ready to go, it’s time to think about how you’re going to adopt new APIs while still being able to ship updates to your app’s current version in the App Store. Remember: you can’t submit App Store builds using the Xcode beta, only TestFlight builds.

There are two main strategies that you can use to address this, depending on the extent of the changes that you’ll be performing for the new operating systems.

The way I’ve been doing things for a long time has been to always create a separate git branch for working on the betas. That way, my beta changes are always completely isolated from the shipping version of the app — making it easy to go back to the current stable branch, make some changes, and submit to the App Store.

Depending on how much work you’ll end up doing on the shipping version of the app, you’ll probably need to constantly merge your shipping changes into your beta branch, as to keep them in sync and to avoid merge conflicts once the beta period is over.

Another option, which is only applicable if the amount of changes you’ll be making is very small, is to implement the new APIs within your main branch, and place that code behind preprocessor macros, so that the code won’t be compiled when building App Store builds of your app.

Let’s say that iOS 14 adds a new delegate method to UITableView and that you’d like to adopt it without creating a separate git branch — then you could do something like this:

#if ENABLE_FALL_2020_APIS
extension MyTableViewDelegate {
    @available(iOS 14.0, *)
    func tableViewDidBecomePuppy(_ tableView: UITableView) {
        // ...
    }
}
#endif

That code won’t even be compiled if the flag ENABLE_FALL_2020_APIS is not defined. To define it, you can either use #define ENABLE_FALL_2020_APIS 1 in a header file, or add -D ENABLE_FALL_2020_APIS to the “Other Swift Flags” build setting in Xcode. Then also make sure that it’s not defined when building for the App Store or when using the non-beta version of Xcode.

Conclusion

Those are some tips that I’ve learned by “living the beta life” over the years. I hope you found something useful that you can adopt within your workflow. If you have additional tips, let me know on Twitter.

Written by: Gui Rambo