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
}