Upgrading To Swift 4.2

If you are upgrading to Xcode 10 and migrating to Swift 4.2 you are likely to see a number of errors because Swift 4.2 has obsoleted and renamed constants and types. It is not as bad as the great renaming of Swift 3 but there is still some maintenance and busy work to do to get back to a clean build.

Xcode Migration

Open an Xcode 9 project in Xcode 10 and it will warn you that a conversion to Swift 4.2 is available:

Xcode upgrade to Swift 4.2

You can also manually change the Swift Language Version in the build settings for a target:

Swift Language Versions

Xcode 10 uses Swift 4.2 by default for new projects. Apple has also stated that this will be the last Xcode release to support Swift 3 mode. So if you plan on maintaining Swift 3 code for a while you may want to hang onto a copy of Xcode 10.

One word of warning from the release notes if you do the conversion to Swift 4.2 with an early Xcode 10 beta:

Swift 4.2 gates important enhancements to the SDK, with some of those enhancements landing in a later beta. While all the new APIs and language features are available without migration, developers must migrate to incorporate the enhancements. Migrating early will identify issues that can be corrected in later betas, but will forego automatic migration of those later SDK changes. (39498127)

So if you run the migrator with an early beta you may have to manually migrate language features that ship in a later beta:

Workaround: Wait until a later beta to try migration or re-run the migrator with non-migrated sources.

The Swiftification of UIKit

The WWDC session by Josh Shaffer on What’s New in Cocoa Touch summarises the main changes. They are not as dramatic as the Swift 3 migration but are a further application of the Swift API guidelines to UIKit and other core frameworks.

In particular there has been a more thorough audit and cleanup of global types, constants and functions:

Nested Types

Global types are now nested under the appropriate class. For example the application state type UIApplicationState is now nested under the UIApplication class:

// Swift 4.1
let appState = UIApplicationState.active

// Swift 4.2
let appState = UIApplication.State.active

Nested Constants

For me, a bigger improvement is the nesting of global constants under their appropriate types. So UIFontTextStyle becomes UIFont.TextStyle.

My favourite of these changes are the notification names which I struggled to remember I needed to prefix with Notification.Name after the last renaming. So Notification.Name.UIContentSizeCategoryDidChange becomes UIContentSizeCategory.didChangeNotification:

// Swift 4.1
NotificationCenter.default.addObserver(self,
  selector: #selector(didChangeTextSize(_:)),
  name: Notification.Name.UIContentSizeCategoryDidChange,
  object: nil)

// Swift 4.2
NotificationCenter.default.addObserver(self,
  selector: #selector(didChangeTextSize(_:)),
  name: UIContentSizeCategory.didChangeNotification,
  object: nil)

What I want from these renaming exercises is that it makes it easier for autocomplete to help me. Typing “contentcategory” in that last example leads me to UIContentSizeCategory which then leads me to the notification name. Time will tell if I find this easier or not.

Another example for the application state notifications. UIApplicationWillEnterForeground becomes UIApplication.willEnterForegroundNotification:

// Swift 4.1
NotificationCenter.default.addObserver(self,
  selector: #selector(willEnterForeground(_:)),
  name: Notification.Name.UIApplicationWillEnterForeground,
  object: nil)

// Swift 4.2
NotificationCenter.default.addObserver(self,
  selector: #selector(willEnterForeground(_:)),
  name: UIApplication.willEnterForegroundNotification,
  object: nil)

Nested Functions

Some odd global functions are now more cleanly nested as methods under the appropriate type. So to inset a CGRect with a UIEdgeInsets used the clumsy UIEdgeInsetsInsetRect(CGRect,UIEdgeInsets) in Swift 4.1.

This becomes a member function of CGRect in Swift 4.2 making for cleaner code that autocomplete can help with:

// Swift 4.1
let container = CGRect(x: 0, y: 0, width: 100, height: 100)
let margin = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
let content = UIEdgeInsetsInsetRect(container, margin)

// Swift 4.2
let container = CGRect(x: 0, y: 0, width: 100, height: 100)
let margin = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
let content = container.inset(by: margin)

Further Details