From Swift 3.2 To 4
My first contact with Apple’s latest version of Swift
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.
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.
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.
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 parameterstransformText(text: "Hello World") { _ in }
// an invalid call in Swift 4transformText(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.2let attributes: [ NSAttributedStringKey : Any ] = [
.font: UIFont.systemFont(ofSize: 12.0)
]
// Swift 4
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!