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?
One-Sided Ranges
To reduce verbosity and improve readability, the standard library can now infer start and end indices using one-sided ranges [SE-0172].
One way this comes in handy is creating a range from an index to the start or end index of a collection:
// Collection Subscript
var planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
let outsideAsteroidBelt = planets[4...] // Before: planets[4..<planets.endIndex]
let firstThree = planets[..<4] // Before: planets[planets.startIndex..<4]
As you can see, one-sided ranges reduce the need to explicitly specify either the start or end index.
Infinite Sequence
They also allow you to define an infinite Sequence
when the start index is a countable type:
// Infinite range: 1...infinity
var numberedPlanets = Array(zip(1..., planets))
print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (8, "Neptune")]
planets.append("Pluto")
numberedPlanets = Array(zip(1..., planets))
print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (9, "Pluto")]
Pattern Matching
Another great use for one-sided ranges is pattern matching:
// Pattern matching
func temperature(planetNumber: Int) {
switch planetNumber {
case ...2: // anything less than or equal to 2
print("Too hot")
case 4...: // anything greater than or equal to 4
print("Too cold")
default:
print("Justtttt right")
}
}
temperature(planetNumber: 3) // Earth
Generic Subscripts
Subscripts are an important part of making data types accessible in an intuative way. To improve their usefulness, subscripts can now be generic [SE-0148]:
struct GenericDictionary<Key: Hashable, Value> {
private var data: [Key: Value]
init(data: [Key: Value]) {
self.data = data
}
subscript<T>(key: Key) -> T? {
return data[key] as? T
}
}
In this example, the return type is generic. You can then use this generic subscript like so:
// Dictionary of type: [String: Any]
var earthData = GenericDictionary(data: ["name": "Earth", "population": 7500000000, "moons": 1])
// Automatically infers return type without "as? String"
let name: String? = earthData["name"]
// Automatically infers return type without "as? Int"
let population: Int? = earthData["population"]
Not only can the return type be generic, but the actual subscript type can be generic as well:
extension GenericDictionary {
subscript<Keys: Sequence>(keys: Keys) -> [Value] where Keys.Iterator.Element == Key {
var values: [Value] = []
for key in keys {
if let value = data[key] {
values.append(value)
}
}
return values
}
}
// Array subscript value
let nameAndMoons = earthData[["moons", "name"]] // [1, "Earth"]
// Set subscript value
let nameAndMoons2 = earthData[Set(["moons", "name"])] // [1, "Earth"]
In this example, you can see that passing in two different Sequence
type (Array
and Set
) results in an array of their respective values.
Miscellaneous
That handles the biggest changes in Swift 4. Now let's go a little more rapidly through some of the smaller bits and pieces.
MutableCollection.swapAt(_:_:)
MutableCollection
now has the mutating method swapAt(_:_:)
which does just as it sounds; swap the values at the given indices [SE-0173]:
// Very basic bubble sort with an in-place swap
func bubbleSort<T: Comparable>(_ array: [T]) -> [T] {
var sortedArray = array
for i in 0..<sortedArray.count - 1 {
for j in 1..<sortedArray.count {
if sortedArray[j-1] > sortedArray[j] {
sortedArray.swapAt(j-1, j) // New MutableCollection method
}
}
}
return sortedArray
}
bubbleSort([4, 3, 2, 1, 0]) // [0, 1, 2, 3, 4]
Associated Type Constraints
You can now constrain associated types using the where
clause [SE-0142]:
protocol MyProtocol {
associatedtype Element
associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element
}
Using protocol constraints, many associatedtype
declarations could constrain their values directly without having to jump through hoops.
Class and Protocol Existential
A feature that has finally made it to Swift from Objective-C is the ability to define a type that conforms to a class as well as a set of protocols [SE-0156]:
protocol MyProtocol { }
class View { }
class ViewSubclass: View, MyProtocol { }
class MyClass {
var delegate: (View & MyProtocol)?
}
let myClass = MyClass()
//myClass.delegate = View() // error: cannot assign value of type 'View' to type '(View & MyProtocol)?'
myClass.delegate = ViewSubclass()
Limiting @objc Inference
To expose or your Swift API to Objective-C, you use the @objc
compiler attribute. In many cases the Swift compiler inferred this for you. The three main issues with mass inference are:
be inferred isn't obvious
- Potential for a significant increase to your binary size
- Knowing when
@objc
will - The increased chance of inadvertently creating an Objective-C selector collisions.
be inferred isn't obvious
Swift 4 takes a stab at solving this by limiting the inference of @objc
[SE-0160]. This means that you'll need to use @objc
explicitly in situations where you want the full dynamic dispatch capabilities of Objective-C.
A few examples of where you'll need to make these changes include private
methods, dynamic
declarations, and any methods of NSObject
subclasses.
NSNumber Bridging
There have been many funky behaviors between NSNumber
and Swift numbers that have been haunting the language for too long. Lucky for us, Swift 4 squashes those bugs [SE-0170].
Here's an example demonstrating an example of the behavior:
let n = NSNumber(value: 999)
let v = n as? UInt8 // Swift 4: nil, Swift 3: 231
The weird behavior in Swift 3 shows that if the number overflows, it simply starts over from 0. In this example, 999 % 2^8 = 231.
Swift 4 solves the issue by forcing optional casting to return a value only if the number can be safely expressed within the containing type.
Swift Package Manager
There's been a number of updates to the Swift Package Manager over the last few months. Some of the biggest changes include:
- Sourcing dependencies from a branch or commit hash
- More control of acceptable package versions
- Replaces unintuitive pinning commands with a more common resolve pattern
- Ability to define the Swift version used for compilation
- Specify the location of source files for each target
These are all big steps towards getting SPM where it needs to be. There's still a long road ahead for the SPM, but it's one that we can all help shape by staying active on the proposals.
For a great overview of what proposals have been recently addressed check out the Swift 4 Package Manager Update.