From Swift 3.2 To 4

My first contact with Apple’s latest version of Swift

Dennis Kiewning
Widgetlabs Engineering
4 min readJul 14, 2017

--

The latest major version of Apple’s programming language Swift is scheduled to be released this fall. Time to discuss some of the changes I had to make when migrating one of our projects from Swift 3.2 to Swift 4. To use Swift 4 you need to download the latest Xcode 9 Beta from Apple’s developer portal first.

Swift 4 is coming fall 2017

Compile Swift 3.2 And Swift 4 Targets Simultaneously

Good news - switching from Swift 3.2 to Swift 4 will be less painful than switching from Swift 2.3 to Swift 3. Xcode 9 can compile two different Swift versions simultaneously. Your project targets may now be either written in Swift 3.2 or Swift 4. This means from now on you can migrate your project step by step. A very welcomed addition considering the impact Swift 4 could have on one of your projects.

Select your Swift Language Version in the Build Settings

Migration Assistant

Like in previous Xcode versions, Xcode 9 will also include a migration tool to make things even easier. Select Edit/Convert and choose To Current Swift Syntax, to let Apple’s IDE edit the codebase for you. Xcode will provide a list of targets included in your project. Select the ones you want to convert and initiate the modifications. However, before you can start Xcode is asking how it should handle the Swift 4 @objc inference. You should select the recommended option to keep your binary size as small as possible.

Minimize Inference to reduce the size of your binary

Limiting @objc Inference

In Swift 3 the rule for @objc annotation to a Swift class was that it must be a descendant of an Objective-C class or it must be marked with the @objc annotation. Which was nice, because every subclass of NSObject already annotated every property and method of that class for you.

So why change it?

There are issues with automatic inference. First of all, your binary size will potentially become bigger. It could also become very difficult to determine when @objc will be inferred and when not. To avoid a situation like this, Swift 4 requires you to use @objc explicitly if you want the full dynamic dispatch capabilities of Objective-C. On the downside, you could end up spending some time just adding @objc annotations when migrating to
Swift 4.

Single Tuple vs. Multiple Argument Function Types

The type system in Swift 3 was not able to distinguish between functions that could take one tuple argument and functions that could take multiple arguments. What does that mean for your codebase? Imagine a situation where you have declared a tuple within an array like this.

typealias City = (name: String, location: String)let cities: [City] = [
(name: "Paris", location: "France"),
(name: "London", location: "England")
]

If you iterate over cities in Swift 3 you’re able to do the following.

cities.forEach { name, location in
print(name) // Paris, London
print(location) // France, England
}

The Swift 3 compiler did extract the elements name and location from the tuple for you. This will no longer work with Swift 4. Swift 4 requires to manually extract tuples from a single parameter.

cities.forEach { city in
print(city.name) // Paris, London
print(city.location) // France, England
}

Another situation where this change becomes relevant is when a function type is declared with more than one parameter. Previously in Swift 3 it was possible to assign a value of a function type which either takes in n parameters or one tuple containing n elements. In Swift 4 however, a function value always needs to take in all declared parameters.

func transformText(
text: String,
completion: ((Error?, String?) -> Void)
)
// function with two parameters
transformText(text: "Hello World") { _ in }
// an invalid call in Swift 4
transformText(text: "Hello World") { _, _ in }
// a valid call in Swift 4

When migrating to Swift 4 Xcode 9 will also suggest that you use () -> () instead of (Void) -> () for the type of a function argument. If you don’t follow this rule you have to add an empty tuple as value for the Void parameter to call such a function: f(())

NSAttributedString Attributes

The NS*AttributeName constants who used to be global String constants are now grouped within their own type NSAttributedStringKey.

[ NSFontAttributeName: UIFont.systemFont(ofSize: 12.0) ]
// Swift 3.2
[ NSAttributedStringKey.font: UIFont.systemFont(ofSize: 12.0) ]
// Swift 4

As a consequence, in Swift 4 the declaration of dictionaries containing string attributes and values also no longer use String as key but NSAttributedStringKey.

let attributes: [ String : Any ] = [
NSFontAttributeName: UIFont.systemFont(ofSize: 12.0)
]
// Swift 3.2
let attributes: [ NSAttributedStringKey : Any ] = [
.font: UIFont.systemFont(ofSize: 12.0)
]
// Swift 4
Finally! :)

Closing Notes

Keep in mind that the above described changes are only the changes I
had to make for one specific project. For a more detailed summary on all modifications which may be needed by Swift 4 make sure to take a look at the following references.

Feel free to leave a comment!

--

--