What’s New in Swift 4.1?
Swift 4.1 is here! What does it mean for you? In this article, you’ll learn about the most significant changes introduced in Swift 4.1 and the impact they will have on your code. By Cosmin Pupăză.
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
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.1?
25 mins
- Getting Started
- Language Improvements
- Conditional Conformance
- Convert Between Camel Case and Snake Case During JSON Encoding
- Equatable and Hashable Protocols Conformance
- Hashable Index Types
- Recursive Constraints on Associated Types in Protocols
- Weak and Unowned References in Protocols
- Index Distances in Collections
- Structure Initializers in Modules
- Platform Settings and Build Configuration Updates
- Build Imports
- Target Environments
- Miscellaneous Bits and Pieces
- Compacting Sequences
- Unsafe Pointers
- New Playground Features
- Where to Go From Here?
Convert Between Camel Case and Snake Case During JSON Encoding
Swift 4.1 lets you convert CamelCase properties to snake_case keys during JSON encoding:
var jsonData = Data()
encoder.keyEncodingStrategy = .convertToSnakeCase
encoder.outputFormatting = .prettyPrinted
do {
jsonData = try encoder.encode(students)
} catch {
print(error)
}
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
When creating your encoder, you set keyEncodingStrategy to .convertToSnakeCase. Looking at your console, you should see:
[
{
"first_name" : "Cosmin",
"average_grade" : 10
},
{
"first_name" : "George",
"average_grade" : 9
}
]
You can also go back from snake case keys to camel case properties during JSON decoding:
var studentsInfo: [Student] = []
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
studentsInfo = try decoder.decode([Student].self, from: jsonData)
} catch {
print(error)
}
for studentInfo in studentsInfo {
print("\(studentInfo.firstName) \(studentInfo.averageGrade)")
}
This time, you set keyDecodingStrategy to .convertFromSnakeCase.
Equatable and Hashable Protocols Conformance
Swift 4 required you to write boilerplate code to make structs conform to Equatable and Hashable:
struct Country: Hashable {
let name: String
let capital: String
static func ==(lhs: Country, rhs: Country) -> Bool {
return lhs.name == rhs.name && lhs.capital == rhs.capital
}
var hashValue: Int {
return name.hashValue ^ capital.hashValue &* 16777619
}
}
Using this code, you implemented ==(lhs:rhs:) and hashValue to support both Equatable and Hashable. You could compare countries, add them to sets and even use them as dictionary keys:
let france = Country(name: "France", capital: "Paris")
let germany = Country(name: "Germany", capital: "Berlin")
let sameCountry = france == germany
let countries: Set = [france, germany]
let greetings = [france: "Bonjour", germany: "Guten Tag"]
Swift 4.1 adds default implementations in structs for Equatable and Hashable as long as all of their properties are Equatable and Hashable as well [SE-0185].
This highly simplifies your code, which can simply be rewritten as:
struct Country: Hashable {
let name: String
let capital: String
}
Enumerations with associated values also needed extra code to work with Equatable and Hashable in Swift 4:
enum BlogPost: Hashable {
case tutorial(String, String)
case article(String, String)
static func ==(lhs: BlogPost, rhs: BlogPost) -> Bool {
switch (lhs, rhs) {
case let (.tutorial(lhsTutorialTitle, lhsTutorialAuthor), .tutorial(rhsTutorialTitle,
rhsTutorialAuthor)):
return lhsTutorialTitle == rhsTutorialTitle && lhsTutorialAuthor == rhsTutorialAuthor
case let (.article(lhsArticleTitle, lhsArticleAuthor), .article(rhsArticleTitle, rhsArticleAuthor)):
return lhsArticleTitle == rhsArticleTitle && lhsArticleAuthor == rhsArticleAuthor
default:
return false
}
}
var hashValue: Int {
switch self {
case let .tutorial(tutorialTitle, tutorialAuthor):
return tutorialTitle.hashValue ^ tutorialAuthor.hashValue &* 16777619
case let .article(articleTitle, articleAuthor):
return articleTitle.hashValue ^ articleAuthor.hashValue &* 16777619
}
}
}
You used the enumeration’s cases to write implementations for ==(lhs:rhs:) and hashValue. This enabled you to compare blog posts and use them in sets and dictionaries:
let swift3Article = BlogPost.article("What's New in Swift 3.1?", "Cosmin Pupăză")
let swift4Article = BlogPost.article("What's New in Swift 4.1?", "Cosmin Pupăză")
let sameArticle = swift3Article == swift4Article
let swiftArticlesSet: Set = [swift3Article, swift4Article]
let swiftArticlesDictionary = [swift3Article: "Swift 3.1 article", swift4Article: "Swift 4.1 article"]
As the case was with Hashable, this code’s size is vastly reduced in Swift 4.1 thanks to default Equatable and Hashable implementations:
enum BlogPost: Hashable {
case tutorial(String, String)
case article(String, String)
}
You just saved yourself from maintaining 20 lines of boilerplate code!
Hashable Index Types
Key paths may have used subscripts if the subscript parameter’s type was Hashable in Swift 4. This enabled them to work with arrays of double; for example:
let swiftVersions = [3, 3.1, 4, 4.1]
let path = \[Double].[swiftVersions.count - 1]
let latestVersion = swiftVersions[keyPath: path]
You use keyPath to get the current Swift version number from swiftVersions.
Swift 4.1 adds Hashable conformance to all index types in the standard library [SE-0188]:
let me = "Cosmin"
let newPath = \String.[me.startIndex]
let myInitial = me[keyPath: newPath]
The subscript returns the first letter of the string. It works since String index types are Hashable in Swift 4.1.
Recursive Constraints on Associated Types in Protocols
Swift 4 didn’t support defining recursive constraints on associated types in protocols:
protocol Phone {
associatedtype Version
associatedtype SmartPhone
}
class IPhone: Phone {
typealias Version = String
typealias SmartPhone = IPhone
}
In this example, you defined a SmartPhone associated type, but it might have proved useful to constrain it to Phone, since all smartphones are phones. This is now possible in Swift 4.1 [SE-0157]:
protocol Phone {
associatedtype Version
associatedtype SmartPhone: Phone where SmartPhone.Version == Version, SmartPhone.SmartPhone == SmartPhone
}
You use where to constrain both Version and SmartPhone to be the same as the phone’s.
Weak and Unowned References in Protocols
Swift 4 supported weak and unowned for protocol properties:
class Key {}
class Pitch {}
protocol Tune {
unowned var key: Key { get set }
weak var pitch: Pitch? { get set }
}
class Instrument: Tune {
var key: Key
var pitch: Pitch?
init(key: Key, pitch: Pitch?) {
self.key = key
self.pitch = pitch
}
}
You tuned an instrument in a certain key and pitch. The pitch may have been nil, so you’d model it as weak in the Tune protocol.
But both weak and unowned are practically meaningless if defined within the protocol itself, so Swift 4.1 removes them and you will get a warning using these keywords in a protocol [SE-0186]:
protocol Tune {
var key: Key { get set }
var pitch: Pitch? { get set }
}
Index Distances in Collections
Swift 4 used IndexDistance to declare the number of elements in a collection:
func typeOfCollection<C: Collection>(_ collection: C) -> (String, C.IndexDistance) {
let collectionType: String
switch collection.count {
case 0...100:
collectionType = "small"
case 101...1000:
collectionType = "medium"
case 1001...:
collectionType = "big"
default:
collectionType = "unknown"
}
return (collectionType, collection.count)
}
typeOfCollection(_:) returned a tuple, which contained the collection’s type and count. You could use it for any kind of collections like arrays, dictionaries or sets; for example:
typeOfCollection(1...800) // ("medium", 800)
typeOfCollection(greetings) // ("small", 2)
You could improve the function’s return type by constraining IndexDistance to Int with a where clause:
func typeOfCollection<C: Collection>(_ collection: C) -> (String, Int) where C.IndexDistance == Int {
// same code as the above example
}
Swift 4.1 replaces IndexDistance with Int in the standard library, so you don’t need a where clause in this case [SE-0191]:
func typeOfCollection<C: Collection>(_ collection: C) -> (String, Int) {
// same code as the above example
}
