Introduction
I’m Daniel Steinberg from Dim Sum Thinking with a quick look at what’s new in Swift 4.
It’s hard to believe that Swift’s only been available for a few years. It’s best to see Swift on a continuum. Swift 2 was Apple’s initial pass at what they thought needed to be added to and changed in the language since its public launch a year before. Swift 3 was the community edition. After open sourcing the language, a lot of changes came from proposals that were submitted by and commented on by the community.
Swift 4 is the release that says, “We need to stabilize the source code.” It also builds on the experience we have using the language for a couple of years on real projects.
Now that we have a better understanding of what Swift wants to be, the proposals adopted in Swift 4 are helping it get there. Swift 5 will be the API Stability release. But there are also long term goals to improve the ownership model, the generics mechanism, and to address concurrency.
So on to what’s new in Swift 4. We’ll talk about the package manager, collections, strings, JSON, access and ownership, generics and types, and quickly look at some of the proposals that were approved, but didn’t make it in. Many of the code examples are from the proposals themselves. The idea is that if there’s something you need to dig into, you can go to the Swift proposal itself and read more.
Package Manager
Let’s start with a very quick look at what’s new in the package manager. Four of the package manager proposals have been part of Swift since 3.1:
- 082 The introduction of Editable Packages
- 145 Version Pinning: The addition of package manager features to pin package dependencies.
- 151 Swift Language Compatibility Version: A new declaration to specify a set of supported Swift language versions
- 152 Tools Version: Another declaration to specify the Swift Tools version.
In Swift 4 the package manager manifest API has been redesigned (158). You’ll also be able to specify products to be built as either executables or libraries (146). While you’re at it, you’re gonna have to explicitly declare your targets, using some new conventions (162).
Two proposals address developing related packages which might depend on each other, or a new package which might not yet have a version (149 & 150). With these two proposals, you can get right to work without worrying about strict versions.
It’s important to know what the platform you’re building for supports. Instead of testing whether the target is Linux or Mac or iOS proposal 75 has this check for support of desired modules such as UIkit or Cocoa.
Remember the pin feature introduced in 3.1? Proposal 175 removes it, and replaces it with a revised dependency resolution mechanism.
Rounding out the package manager additions are a run command to build and run an executable (179), and C/C++ language support (181). That’s the package manager.
Collections
172 One Sided Ranges
One of my favorite little additions to Swift is One Sided Ranges. For example, let’s create a howBig
function:
func howBig(is number: Int) -> String {
switch number {
case 0...4:
return "Less than 5"
case 5...9:
return "\(number)"
case 10...100:
return "Greater than 10"
default:
return "Greater than 10"
}
}
It takes an Int
and returns a String
. Let’s switch on the Int
. If it’s between 0 and 4, we’ll return “Less than 5”. If it’s between 5 and 9 we’ll return the number. If it’s greater than 10 we’ll return “Greater than 10”. So how big is 3 returns “Less than 5”.
We can use one sided ranges to write this without the lower limit: case ...4:.
In this case we’re now including negative numbers as well. So how big is -2 returns “Less than 5”. We can also use a less than but not equals as a one sided range: case ..<5:
.
Similarly, we can take care of any number greater than 10 using a one sided limit on the other side: case 10...:
. The default case is then the numbers from 5 to 9 as those are all that remain, so we can clean up our example a bit. Here’s the final example:
func howBig(is number: Int) -> String {
switch number {
case ..<5:
return "Less than 5"
case 10...:
return "Greater than 10"
default:
return "\(number)"
}
}
We can also use one sided ranges in arrays. Here’s an array of strings: let letters = ["a", "b", "c", "d", "e"]
. We use a one sided range with an unspecified lower limit so the index starts at 0: letters[...2]
returns ["a", "b", "c"]
.
And here we use an unspecified upper limit, so the index ends at the array count minus one: letters[2...]
returns ["c", "d", "e"]
.
165 Dictionary & Set Enhancements
There are many enhancements to dictionaries and sets. Here are a few dictionary improvements. In this example we start with the string “how now brown cow.”
let source = "how now brown cow"
var frequencies: [Character: Int] = [:]
for c in source.characters {
if frequencies[c] == nil {
frequencies[c] = 1
} else {
frequencies[c]! += 1
}
}
We’re going to check for the frequency of the characters. The first time we encounter a character no frequency has been recorded for it, its frequency is nil
. So if it’s the first time we’ll initialize the frequency for this value to be 1. Otherwise, if it’s not the first time then we’ll increment the frequency count.
In Swift 4 we can combine these two cases by providing the default value of zero and incrementing the frequency count whether it’s the first time we’re seeing this character or not:
let source = "how now brown cow"
var frequencies: [Character: Int] = [:]
for c in source.characters {
frequencies[c, default: 0] += 1
}
So if there’s no value, we use the default of 0, and if there is a value we use it.
Let’s create a simple dictionary to use to see other new features:
let numbers = ["one": 1, "two": 2, "three": 3, "four": 4]
Up until now, whenever we filter a sequence the result is an array. In Swift 4, if we filter a dictionary, we get back a dictionary, which is certainly what you prefer:
let evens = numbers.filter { $0.value % 2 == 0 }
["four": 4, "two": 2]
If we map the dictionary we get an array, but often we’d like to apply a function to the values of the dictionary and preserve the key value pairs. Map still returns an array, but dictionary has a new method named mapValues
that does just that – it applies the closure to the values while keeping the values together with their keys. Here, I’m increasing each value by one:
let offByOne = numbers.mapValues {$0 + 1 }
["three": 4, "four": 5, "one": 2, "two": 3]
Finally, we can create a dictionary containing items that are grouped somehow. In this case, we grouped the keys from the numbers dictionary by their first letter:
let grouped = Dictionary(grouping: numbers.keys,
by: { $0.characters.first! })
["t": ["three", "two"], "f": ["four"], "o": ["one"]]
154 Provide Custom Collections for Dictionary Keys and Values
The type for a dictionary’s keys and values have changed with every release of Swift. As of Swift 4 there are new collection types named Keys and Values that represent the keys and values of a dictionary.
These three proposals add new methods to the standard library to make collections easier to work with:
- 045 Add
prefix(while:)
anddrop(while:)
to the sdlib - 173 Add
MutableCollection.swapAt(_:_:)
- 171 Reduce
with inout
Strings
One of my favorite things about strings in Swift 4 is that they’re collections of characters. I know, they used to be collections then they weren’t. Well, now they are again.
163 String Revision
Formally, strings are both BidirectionalCollection
s and RangeReplaceableCollection
s. This means that if we look at a previous example we don’t have to ask the source string for its characters, we can just fast enumerate source directly and c is inferred to be a character:
let source = "how now brown cow"
var frequencies: [Character: Int] = [:]
for c in source {
frequencies[c, default: 0] += 1
}
There were related proposals to make all of this work including the String Index Overhaul (180), and the addition of a Unicode scalars property to character (178).
Because a string is a collection, we can also apply one sided ranges to a string. Here we find the index of a character and use a one sided range to pull out the characters prior to but not including that character. This makes parsing strings much nicer:
let source = "how now brown cow"
if let bIndex = source.index(of: "b") {
source[..<bIndex]
}
"how now "
An additional part of this proposal is that the resulting substring is not a string, it’s a new type called Substring. Both String and Substring conform to a new string protocol. The whole point of substrings is to improve performance when operating on strings, and a separate proposal adds some affordances for substring performance (183).
168 Multiline String Literals
With Swift 4 we have two convenience additions for dealing with strings allowing us to use strings that might span multiple lines. Here I enclose a poem in a triplet of quotation marks:
let multiline = """
I wrote a poem
that didn't rhyme
Just to exhibit
multiline
"""
Now note that the enclosed string begins on the line following the three open quotation marks. I use a poem here, but you’ll more likely see something like an XML, HTML, JSON or other code listing. The result is the string with newline characters inserted: "I wrote a poem\nthat didn't rhyme\nJust to exhibit\nmultiline"
.
182 String Newline Escaping
There are other times when we need to start a new line for readability in our code but we don’t want the string to be split at that point. This proposal allows for a newline escaping symbol:
let multiline = """
I wrote a poem \
that didn't rhyme \
Just to exhibit \
multiline
"""
"I wrote a poem that didn't rhyme Just to exhibit multiline"
You can see that the spaces before the symbol are respected. What you can’t really see is that also whitespace characters between the symbol and the newline are ignored.
Multiline strings make it easy to display JSON, but of course display is not the real problem with JSON in Swift.
JSON
In Swift 4, many of the nested ifs, failable inits, typecasting and other clever methods for converting class, struct, and enumeration instances to and from JSON, disappear.
166 Swift Archival and Serialization
The Swift Archival and Serialization proposal is part of the trio of proposals that make it easier to serialize to plist and JSON.
Participating types need to confirm to the Codable protocol. Codable itself is made up of the Encodable and Decodable protocols. Even better, when you declare a type that has properties which are themselves Codable to be Codable, it’s automatically Codable. Otherwise it’s up to you to conform to these protocols yourself.
Access and Ownership
There are a handful of proposals that have to do with access and ownership. The first just makes me laugh.
169 Improve Interaction Between private
Declarations and Extensions
The meaning of private
has changed in every release of Swift, and this one is no exception. In Swift 2, private
meant visible to anything inside the same file.
In Swift 3, the word fileprivate
was introduced to describe this meaning and the keyword private
was changed to mean that access was restricted to elements in the same context within the same file.
This meant if you had a property in a class that you wanted to access in an extension in the same file, you had to declare that that property was fileprivate
. It seems that this was a problem for some, so now private
has changed so that types and their extensions in the same file can access private members.
class Sample {
private let myProperty = "Hello!"
}
extension Sample : CustomStringConvertible {
var description: String {
return myProperty
}
}
160 Limiting @objc
Inference
It might be a stretch to include Limiting @objc Inference in the category of Access and Ownership, but it does cover APIs that need to be accessed from Objective-C. Most instances automatically inferring @objc
are removed, as they add unnecessary overhead.
Cases that remain include the case where we override a method that was declared with @objc
, the case where a method conforms to an @objc
protocol, in the case of an @IBOutlet
or @IBAction
, or if it’s managed by core data, denoted with NSManaged
. Similarly, additional cases for which @objc will be inferred are @IBInspectableProperties
and GKInspectableAttributes
.
141 Availability by Swift Version
In the past we declared that a member was available for a specific iOS version, or some range of iOS versions. As of Swift 4 we can declare availability by Swift version:
@available(swift, obsoleted: 3.1)
class Foo {
//...
}
164 Remove final support in Protocol Extensions
This is more of a cleanup proposal. Methods and protocol extensions can’t be overridden, so the final keyword has no meaning. In this proposal, its use is prohibited.
Enforce Exclusive Access to Memory
This next proposal is huge. You should take time to read the Ownership Manifesto and to see the portion of the What’s New in Swift session from WWDC that goes over ownership. The manifesto and the session go over many problems in overlapping access of variables.
The high level takeaway for where the Swift ownership model is heading is that you don’t want someone to be writing to a variable while you’re in the process of reading it, and you never want two people to be writing to the same variable.
In other words, the goal is that two accesses of the same variable are not allowed to overlap unless both accesses are reads.
Generics and Types
104 Protocol-Oriented Integers
The Protocol Oriented Integers proposal is designed to clean up Swift’s integer APIs with a new set of protocols that makes them more suited for generic programming. This isn’t going to help you do arithmetic with two different types of Int
s, but it does lay the groundwork for that. It also lays the groundwork for but does not implement a BigInt
type.
170 NSNumber
bridging and Numeric Types
Another collection of number related fixes is addressed in this proposal for NSNumber
bridging and Numeric Types. The main goal here is to fix cases in which you might get unexpected behavior when working with numbers.
156 Class and Subtype Existentials
In Objective-C we have the ability to declare that the type of a method parameter is an instance or subclass of one class, at the same time that it conforms to one or more protocols using one of these declarations:
id<Protocol1, Protocol2>
Base<Protocol1, Protocol2>*
We’ve not been able to do this in Swift until now. As of Swift 4 we can specify this conforms like this:
AnyObject & Protocol1 & Protocol2
Base & Protocol1 & Protocol2
So for example we can say that something is a UITextView that also conforms to this protocol and that protocol. The proposal also details the protections the implementation provides that keeps us from running into clashes in requirements.
148 Generic Subscripts
The Generic Subscripts proposal makes it possible for a subscript to provide a generic return type and/or for the subscripts themselves to be generics. This is a small piece of the larger generic manifesto, which will take many more Swift releases to be completed.
142 Permit where
Clauses to Constrain Associated Types
Finally, associated types can now have where
clauses to more flexibly constrain the associated types.
What about…?
There were 10 proposals that were accepted but not implemented in time to ship with Swift 4.
Given the attention given to Smart KeyPaths at WWDC it was a bit surprising that Smart Keypaths: Better Key-Value Coding for Swift (161) didn’t make it into the Swift 4 release. Two of the generic proposals, Conditional Constraints (143), and Support Recursive Constraints on Associated Types (157) also weren’t finished in time. The second leg of the Codable trio, Swift Encoders (167), was not implemented, and this collection also remains undone:
- 185 Synthesizing Equatable and Hashable Conformance
- 174 Change Filter to return and associated type
- 153 Compensate for inconsistency of @NSCopying’s behavior
- 042 Flattening the function type of unapplied method references
- 155 Normalize Enum Case Representation
- 068 Expanding Swift Self to class members and value types
Swift 5
I suppose that gives us concrete features to look forward to in Swift 5 or beyond, but remember, Swift 5 is committed to ABI stability. This was a goal in Swift 4 and even in Swift 3. Now it’s stated more firmly as a commitment, and the single most important requirement of the Swift 5 release. Expect more work in generics, ownership, and possibly concurrency.
Swift 4 is a powerful and stable language from a developer’s standpoint. I hope you’ve enjoyed this quick look at what’s new in Swift 4.
About the content
This content has been published here with the express permission of the author.