Xcode 9.3 and Swift 4.1 are finally no longer beta! This release contains long-awaited improvements to the standard library and the language itself. If you do not follow the
Swift Evolution process , then this article is for you.
In this article you will learn about the most significant changes made in
Swift 4.1 .
This article requires Xcode 9.3, so make sure that this version of Xcode is installed.
The first steps
Swift 4.1 is compatible with Swift 4 source code, so new features do not violate your code if you have already migrated your project to Swift 4 with
Swift Migrator in Xcode.
')
In the following sections, you will see related tags, such as [SE-0001]. These are the
Swift Evolution offer numbers. I added a link to each proposal so that you can understand the full information about each particular change. I recommend that you try out the functions in practice using the Playground in order to better understand exactly what will change in your work.
To get started, launch Xcode 9.3 and select
File ▸ New ▸ Playground . Select
iOS as the platform and
Blank as the template. Name and save it on your own. To get the most out of this article, try to practice each function in the Playground.
Note: If you missed what was changed in Swift 4 and are going to catch up? No problems! Check out Swift 4 by reading What's New in Swift 4 .Language enhancements
This release contains a number of language enhancements, including conditional matching, recursive constraints on related types in protocols, and more.
Conditional match
Conditional matching allows protocol matching for generic types, where type arguments satisfy certain conditions
[SE-0143] . This is a powerful feature that makes code more flexible. You can see how it works with a few examples.
Conditional match in the standard library
In Swift 4, you can compare arrays, dictionaries, and optionals if their elements conform to the
Equatable protocol. This worked perfectly fine for basic scenarios, such as:
// Arrays of Int let firstArray = [1, 2, 3] let secondArray = [1, 2, 3] let sameArray = firstArray == secondArray // Dictionaries with Int values let firstDictionary = ["Cosmin": 10, "George": 9] let secondDictionary = ["Cosmin": 10, "George": 9] let sameDictionary = firstDictionary == secondDictionary // Comparing Int? let firstOptional = firstDictionary["Cosmin"] let secondOptional = secondDictionary["Cosmin"] let sameOptional = firstOptional == secondOptional
Using the
== operator to check for equality in these examples was quite fair, since
Int is
Equatable in Swift 4. However, comparing option sets is a common situation you might encounter in Swift 4, because the
options do not comply with the
Equatable protocol. Swift 4.1 corrects this problem using conditional correspondence, allowing you to compare additional types with those types that underlie
Equatable :
// Array of Int? let firstArray = [1, nil, 2, nil, 3, nil] let secondArray = [1, nil, 2, nil, 3, nil] let sameArray = firstArray == secondArray // Dictionary with Int? values let firstDictionary = ["Cosmin": 10, "George": nil] let secondDictionary = ["Cosmin": 10, "George": nil] let sameDictionary = firstDictionary == secondDictionary // Comparing Int?? (Optional of Optional) let firstOptional = firstDictionary["Cosmin"] let secondOptional = secondDictionary["Cosmin"] let sameOptional = firstOptional == secondOptional
Int? is
Equatable in Swift 4.1, so the
== operator works for
[Int?], [String: Int?] and
Int ?? .
A similar problem was solved when comparing arrays (for example, [[Int]]). In Swift 4, you can compare arrays of sets (for example, [Set </ Int />]), since sets correspond to the
Equatable protocol. Swift 4.1 solves this, because arrays (and dictionaries), as well as their basic values, are
Equatable .
let firstArrayOfSets = [Set([1, 2, 3]), Set([1, 2, 3])] let secondArrayOfSets = [Set([1, 2, 3]), Set([1, 2, 3])] // Will work in Swift 4 and Swift 4.1 // since Set<Int> is Equatable firstArrayOfSets == secondArrayOfSets let firstArrayOfArrays = [[1, 2, 3], [3, 4, 5]] let secondArrayOfArrays = [[1, 2, 3], [3, 4, 5]] // Caused an error in Swift 4, but works in Swift 4.1 // since Arrays are Equatable in Swift 4.1 firstArrayOfArrays == secondArrayOfArrays
As a rule,
Optional ,
Array and
Dictionary in Swift 4.1 now conform to
Equatable and
Hashable , whenever their base values or elements conform to these protocols.
Here is an example of how conditional matches work in the standard library. Then you implement it in your code.
Conditional correspondence in practice
Now we use conditional correspondence to create our own group of musical instruments. Add the following code block to the
Playground .
// 1 class LeadInstrument: Equatable { let brand: String init(brand: String) { self.brand = brand } func tune() -> String { return "Standard tuning." } static func ==(lhs: LeadInstrument, rhs: LeadInstrument) -> Bool { return lhs.brand == rhs.brand } } // 2 class Keyboard: LeadInstrument { override func tune() -> String { return "Keyboard standard tuning." } } // 3 class Guitar: LeadInstrument { override func tune() -> String { return "Guitar standard tuning." } }
Here is what this code does step by step:
- The LeadInstrument class conforms to the Equatable protocol. It has a specific brand and tune () method that you will use to configure the tool.
- You override the tune () method in the Keyboard class to return the default settings for the object.
- You do the same for the Guitar class.
Then declare a group of tools:
// 1 class Band<LeadInstrument> { let name: String let lead: LeadInstrument init(name: String, lead: LeadInstrument) { self.name = name self.lead = lead } } // 2 extension Band: Equatable where LeadInstrument: Equatable { static func ==(lhs: Band<LeadInstrument>, rhs: Band<LeadInstrument>) -> Bool { return lhs.name == rhs.name && lhs.lead == rhs.lead } }
Here is what you are doing step by step:
- You create a class of the Band type - LeadInstrument. Each group has a unique name and a lead instrument.
- You use where the Band conforms to the Equatable protocol, just like LeadInstrument performs certain actions. This is where conditional correspondence manifests itself - you can assign compliance to Equatable protocol for the generic LeadInstruments.
Then create your favorite groups of tools and compare them:
// 1 let rolandKeyboard = Keyboard(brand: "Roland") let rolandBand = Band(name: "Keys", lead: rolandKeyboard) let yamahaKeyboard = Keyboard(brand: "Yamaha") let yamahaBand = Band(name: "Keys", lead: yamahaKeyboard) let sameBand = rolandBand == yamahaBand // 2 let fenderGuitar = Guitar(brand: "Fender") let fenderBand = Band(name: "Strings", lead: fenderGuitar) let ibanezGuitar = Guitar(brand: "Ibanez") let ibanezBand = Band(name: "Strings", lead: ibanezGuitar) let sameBands = fenderBand == ibanezBand
In this code snippet, you create two Keyboards and Guitar along with their respective Bands. Then you compare Bands directly, thanks to the conditional match that you identified earlier.
Conditional match in JSON parsing
In Swift 4.1, arrays, dictionaries, sets and additional modules correspond to the Codable protocol, if their elements also conform to this protocol. Add the following code to the playground:
struct Student: Codable, Hashable { let firstName: String let averageGrade: Int } let cosmin = Student(firstName: "Cosmin", averageGrade: 10) let george = Student(firstName: "George", averageGrade: 9) let encoder = JSONEncoder() // Encode an Array of students let students = [cosmin, george] do { try encoder.encode(students) } catch { print("Failed encoding students array: \(error)") } // Encode a Dictionary with student values let studentsDictionary = ["Cosmin": cosmin, "George": george] do { try encoder.encode(studentsDictionary) } catch { print("Failed encoding students dictionary: \(error)") } // Encode a Set of students let studentsSet: Set = [cosmin, george] do { try encoder.encode(studentsSet) } catch { print("Failed encoding students set: \(error)") } // Encode an Optional Student let optionalStudent: Student? = cosmin do { try encoder.encode(optionalStudent) } catch { print("Failed encoding optional student: \(error)") }
Do you use this code for encode
[Student] ,
[String: Student] , Set </ Student /> and
Student? . This code works well in Swift 4.1, since Student is
Codable , which makes these types of collections also
Codable compliant .
Conversion between CamelCase and Snake_Case when working with JSON
Swift 4.1 allows you to convert
CamelCase properties to
snake_case keys during JSON parsing:
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 you create an
encoder object, you set the
keyEncodingStrategy property to
.convertToSnakeCase . Looking at the console, you should see:
[ { "first_name" : "Cosmin", "average_grade" : 10 }, { "first_name" : "George", "average_grade" : 9 } ]
You can also convert back from snake_case to CamelCase while working with JSON:
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, for the
keyDecodingStrategy property
, you assign the value
.convertFromSnakeCase .
Equatable and Hashable protocol compliance and compatibility
In Swift 4, it was required to write template code so that the structures conform to the
Equatable and
Hashable protocols :
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 implement
== (lhs: rhs :) and
hashValue to support both
Equatable and
Hashable . You can compare
Country objects, add them to
Set’s and even use them as keys for a dictionary:
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 for the structures of the corresponding
Equatable and
Hashable , since all its properties are
Equalable and
Hashable [
SE-0185 ].
This greatly simplifies your code, which you can simply rewrite as:
struct Country: Hashable { let name: String let capital: String }
Enumerations with related values also require additional 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 enumeration cases to write implementations
== (lhs: rhs :) and
hashValue . This allowed 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"]
Unlike
Hashable , the size of this code is significantly smaller in Swift 4.1, thanks to the
Equatable and
Hashable implementations :
enum BlogPost: Hashable { case tutorial(String, String) case article(String, String) }
You just saved yourself from working with 20 lines of template code!
Hashable Index Types
Key paths could use indices if the index parameter type was Hashable in Swift 4. This allowed them to work with double arrays; eg:
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 matching
Hashable to all types of subscripts in the standard library [
SE-0188 ]:
let me = "Cosmin" let newPath = \String.[me.startIndex] let myInitial = me[keyPath: newPath]
By index is returned the first letter of the string. It works because String index types are
Hashable in Swift 4.1.
Recursive constraints on related types in protocols
Swift 4 did not support the definition of recursive constraints on related types in protocols:
protocol Phone { associatedtype Version associatedtype SmartPhone } class IPhone: Phone { typealias Version = String typealias SmartPhone = IPhone }
In this example, you have identified the type associated with the SmartPhone, but it might be useful to limit it to the 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 limit both Version and SmartPhone so that they are the same as on the phone.
Weak and not busy links in the protocols
Swift 4 supports
weak and
unowned 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 set up the instrument in a specific key and pitch. The pitch may have been zero, so you'll be modeling it as weak in the Tune protocol.
But both weak and unowned are practically meaningless if they are defined in the protocol itself, therefore Swift 4.1 removes them and you will receive a warning using these keywords in the 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 items in the 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) }
The
typeOfCollection (_ :) method returns a tuple that contains the type and number of the collection. You can use it for any collections, such as arrays, dictionaries or sets; eg:
typeOfCollection(1...800) // ("medium", 800) typeOfCollection(greetings) // ("small", 2)
You can improve the return type of the function by limiting
IndexDistance to
Int with the
where clause:
func typeOfCollection<C: Collection>(_ collection: C) -> (String, Int) where C.IndexDistance == Int {
Swift 4.1 replaces
IndexDistance with Int in the standard library, so in this case you do not need the
where clause [
SE-0191 ]:
func typeOfCollection<C: Collection>(_ collection: C) -> (String, Int) {
Structure initializers in modules
Adding properties to
public structures can lead to initial changes in Swift 4. In this article, make sure that the Project Navigator is visible in Xcode by going to the
View \ Navigators \ Show menu of the
Project Navigator. Then right-click “Sources” and select “New File” in the menu. Rename the file
DiceKit.swift . Replace its contents with the following code block:
public struct Dice { public let firstDie: Int public let secondDie: Int public init(_ value: Int) { let finalValue: Int switch value { case ..<1: finalValue = 1 case 6...: finalValue = 6 default: finalValue = value } firstDie = finalValue secondDie = 7 - finalValue } }
The structure initializer ensures that both dice have valid values between 1 and 6. Return to the
Playground and add this code at the end:
// 1 let dice = Dice(0) dice.firstDie dice.secondDie // 2 extension Dice { init(_ firstValue: Int, _ secondValue: Int) { firstDie = firstValue secondDie = secondValue } } // 3 let newDice = Dice(0, 7) newDice.firstDie newDice.secondDie
Here is what you did with this code:
- You have created a valid pair of dice.
- You have added Dice through another initializer that has direct access to its properties.
- You have defined an invalid pair of dice with a new structure initializer.
In Swift 4.1,
cross-target initializers should cause a default value. Change the Dice extension to:
extension Dice { init(_ firstValue: Int, _ secondValue: Int) { self.init(abs(firstValue - secondValue)) } }
This change leads to the fact that the structures behave like classes: the initializers of the cross-modules must be convenience initializers in Swift 4.1 [
SE-0189 ].
In Swift 4.1, you can no longer fool the dice!
Platform Settings and Configuration Updates
Swift 4.1 adds some necessary platform functions and builds to test the code:
Build Imports / Build Imports
In Swift 4, you tested the module if it is available on a specific platform, determining the operating system itself: for example:
#if os(iOS) || os(tvOS) import UIKit print("UIKit is available on this platform.") #else print("UIKit is not available on this platform.") #endif
UIKit is available on
iOS and
tvOS , so you imported it if the test was successful. Swift 4.1 simplifies this process by allowing you to verify the module itself:
#if canImport(UIKit) print("UIKit is available if this is printed!") #endif
In Swift 4.1, you use
#if canImport (UIKit) to confirm that a certain structure is available for import [
SE-0075 ].
Target Environments
When writing code in Swift 4, the most famous way to verify the execution of code on a simulator or physical device was to check the architecture and operating system:
#if (arch(i386) || arch(x86_64)) && (os(iOS) || os(tvOS) || os(watchOS)) print("Testing in the simulator.") #else print("Testing on the device.") #endif
Whether your processor architecture was based on Intel, and your operating system was iOS, tvOS or watchOS, you tested it in a simulator. Otherwise, you tested the device.
This test was very cumbersome, and it also did not fully describe the type of errors. Swift 4.1 makes this test easier; just use targetEnvironment (simulator) [SE-0190] as follows:
#if targetEnvironment(simulator) print("Testing in the simulator.") #endif
Miscellaneous Bits and Pieces
Swift 4.1 has a few more updates worth knowing:
Compacting Sequences
In Swift 4, the use of
flatMap (_ :) to filter nil values from a sequence was quite common:
let pets = ["Sclip", nil, "Nori", nil] let petNames = pets.flatMap { $0 }
Unfortunately,
flatMap (_ :) was overloaded in various ways and, in this particular scenario, the
flatMap (_ :) assignment did not really describe the actions taken.
For these reasons, Swift 4.1 introduces the renaming of
flatMap (_:) to
compactMap (_ :) , to make its meaning clearer and more unique [
SE-0187 ]:
let petNames = pets.compactMap {$ 0}
Unsafe Pointers / Unsafe Pointers
Swift 4 used temporary unsafe editable pointers to create and modify unsafe mutable pointer pointers:
let buffer = UnsafeMutableBufferPointer<Int>(start: UnsafeMutablePointer<Int>.allocate(capacity: 10), count: 10) let mutableBuffer = UnsafeMutableBufferPointer(start: UnsafeMutablePointer(mutating: buffer.baseAddress), count: buffer.count)
Swift 4.1 allows you to work with unsafe mutable pointer pointers directly, using the same approach as with unsafe mutable pointers [
SE-0184 ]:
New Playground Features
Swift 4 let you customize type descriptions in the
Playground Xcode:
class Tutorial {} extension Tutorial: CustomPlaygroundQuickLookable { var customPlaygroundQuickLook: PlaygroundQuickLook { return .text("raywenderlich.com tutorial") } } let tutorial = Tutorial()
You have implemented
CustomPlaygroundQuickLookable for the
Tutorial and return a brief description. The description type in
customPlaygroundQuickLook was limited to
PlaygroundQuickLook instances. there is no such pun in Swift 4.1:
extension Tutorial: CustomPlaygroundDisplayConvertible { var playgroundDescription: Any { return "raywenderlich.com tutorial" } }
This time you are implementing a
CustomPlaygroundDisplayConvertible . The description type is
Any , so you can return anything from playgroundDescription. This simplifies your code and makes it more flexible [
SE-0198 ].
What next?
Swift 4.1 improves on some of the features of Swift 4 in preparation for more serious changes that will appear in Swift 5 this year. These include ABI stability, improved generics and strings, new memory ownership and concurrency models, and more.
If you feel like an adventurer, go and look at
the Swift standard library or on the
Swift CHANGELOG official website, where you can read more information about all the changes in this version.
If you're wondering what changes will be in Swift 5, we also recommend that you familiarize yourself with
Swift Evolution's offers , where you can see new features, changes and additions.