What’s New in Swift 4?
Learn about what is new in Swift 4. By Eric Cerney.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
What’s New in Swift 4?
30 mins
- Getting Started
- Migrating to Swift 4
- API Changes
- Strings
- Dictionary and Set
- Private Access Modifier
- API Additions
- Archival and Serialization
- Key-Value Coding
- Multi-line String Literals
- One-Sided Ranges
- Generic Subscripts
- Miscellaneous
- MutableCollection.swapAt(_:_:)
- Associated Type Constraints
- Class and Protocol Existential
- Limiting @objc Inference
- NSNumber Bridging
- Swift Package Manager
- Still In Progress
- Where to Go From Here?
Private Access Modifier
An element of Swift 3 some haven't been too fond of was the addition of fileprivate
. In theory, it's great, but in practice its usage can often be confusing. The goal was to use private
within the member itself, and to use fileprivate
rarely in situations where you wanted to share access across members within the same file.
The issue is that Swift encourages using extensions to break code into logical groups. Extensions are considered outside of the original member declaration scope, which results in the extensive need for fileprivate
.
Swift 4 realizes the original intent by sharing the same access control scope between a type and any extension on said type. This only holds true within the same source file [SE-0169]:
struct SpaceCraft {
private let warpCode: String
init(warpCode: String) {
self.warpCode = warpCode
}
}
extension SpaceCraft {
func goToWarpSpeed(warpCode: String) {
if warpCode == self.warpCode { // Error in Swift 3 unless warpCode is fileprivate
print("Do it Scotty!")
}
}
}
let enterprise = SpaceCraft(warpCode: "KirkIsCool")
//enterprise.warpCode // error: 'warpCode' is inaccessible due to 'private' protection level
enterprise.goToWarpSpeed(warpCode: "KirkIsCool") // "Do it Scotty!"
This allows you to use fileprivate
for its intended purpose rather than as a bandaid to code organization.
API Additions
Now let's take a look at the new shinny features of Swift 4. These changes shouldn't break your existing code as they are simply additive.
Archival and Serialization
Up to this point in Swift, to serialize and archive your custom types you'd have to jump through a number of hoops. For class
types you'd need to subclass NSObject
and implement the NSCoding
protocol.
Value types like struct
and enum
required a number of hacks like creating a sub object that could extend NSObject
and NSCoding
.
Swift 4 solves this issue by bringing serialization to all three Swift types [SE-0166]:
struct CuriosityLog: Codable {
enum Discovery: String, Codable {
case rock, water, martian
}
var sol: Int
var discoveries: [Discovery]
}
// Create a log entry for Mars sol 42
let logSol42 = CuriosityLog(sol: 42, discoveries: [.rock, .rock, .rock, .rock])
In this example you can see that the only thing required to make a Swift type Encodable
and Decodable
is to implement the Codable
protocol. If all properties are Codable
, the protocol implementation is automatically generated by the compiler.
To actually encode the object, you'll need to pass it to an encoder. Swift encoders are being actively implemented in Swift 4. Each encodes your objects according to different schemes [SE-0167] (Note: Part of this proposal is still in development):
let jsonEncoder = JSONEncoder() // One currently available encoder
// Encode the data
let jsonData = try jsonEncoder.encode(logSol42)
// Create a String from the data
let jsonString = String(data: jsonData, encoding: .utf8) // "{"sol":42,"discoveries":["rock","rock","rock","rock"]}"
This took an object and automatically encoded it as a JSON object. Make sure to check out the properties JSONEncoder
exposes to customize its output.
The last part of the process is to decode the data back into a concrete object:
let jsonDecoder = JSONDecoder() // Pair decoder to JSONEncoder
// Attempt to decode the data to a CuriosityLog object
let decodedLog = try jsonDecoder.decode(CuriosityLog.self, from: jsonData)
decodedLog.sol // 42
decodedLog.discoveries // [rock, rock, rock, rock]
With Swift 4 encoding/decoding you get the type safety expected in Swift without relying on the overhead and limitations of @objc
protocols.
Key-Value Coding
Up to this point you could hold reference to functions without invoking them because functions are closures in Swift. What you couldn't do is hold reference to properties without actually accessing the underlying data held by the property.
A very exciting addition to Swift 4 is the ability to reference key paths on types to get/set the underlying value of an instance [SE-0161]:
struct Lightsaber {
enum Color {
case blue, green, red
}
let color: Color
}
class ForceUser {
var name: String
var lightsaber: Lightsaber
var master: ForceUser?
init(name: String, lightsaber: Lightsaber, master: ForceUser? = nil) {
self.name = name
self.lightsaber = lightsaber
self.master = master
}
}
let sidious = ForceUser(name: "Darth Sidious", lightsaber: Lightsaber(color: .red))
let obiwan = ForceUser(name: "Obi-Wan Kenobi", lightsaber: Lightsaber(color: .blue))
let anakin = ForceUser(name: "Anakin Skywalker", lightsaber: Lightsaber(color: .blue), master: obiwan)
Here you're creating a few instances of force users by setting their name, lightsaber, and master. To create a key path, you simply use a back-slash followed by the property you're interested in:
// Create reference to the ForceUser.name key path
let nameKeyPath = \ForceUser.name
// Access the value from key path on instance
let obiwanName = obiwan[keyPath: nameKeyPath] // "Obi-Wan Kenobi"
In this instance, you're creating a key path for the name
property of ForceUser
. You then use this key path by passing it to the new subscript keyPath
. This subscript is now available on every type by default.
Here are more examples of ways to use key paths to drill down to sub objects, set properties, and build off key path references:
// Use keypath directly inline and to drill down to sub objects
let anakinSaberColor = anakin[keyPath: \ForceUser.lightsaber.color] // blue
// Access a property on the object returned by key path
let masterKeyPath = \ForceUser.master
let anakinMasterName = anakin[keyPath: masterKeyPath]?.name // "Obi-Wan Kenobi"
// Change Anakin to the dark side using key path as a setter
anakin[keyPath: masterKeyPath] = sidious
anakin.master?.name // Darth Sidious
// Note: not currently working, but works in some situations
// Append a key path to an existing path
//let masterNameKeyPath = masterKeyPath.appending(path: \ForceUser.name)
//anakin[keyPath: masterKeyPath] // "Darth Sidious"
The beauty of key paths in Swift is that they are strongly typed! No more of that Objective-C string style mess!
Multi-line String Literals
A very common feature to many programming languages is the ability to create a multi-line string literal. Swift 4 adds this simple but useful syntax by wrapping text within three quotes [SE-0168]:
let star = "⭐️"
let introString = """
A long time ago in a galaxy far,
far away....
You could write multi-lined strings
without "escaping" single quotes.
The indentation of the closing quotes
below deside where the text line
begins.
You can even dynamically add values
from properties: \(star)
"""
print(introString) // prints the string exactly as written above with the value of star
This is extremely useful when building XML/JSON messages or when building long formatted text to display in your UI.