📜 ⬆️ ⬇️

What's new in Swift 4.1?

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.

Swift 4.1

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:

  1. The LeadInstrument class conforms to the Equatable protocol. It has a specific brand and tune () method that you will use to configure the tool.
  2. You override the tune () method in the Keyboard class to return the default settings for the object.
  3. 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:

  1. You create a class of the Band type - LeadInstrument. Each group has a unique name and a lead instrument.
  2. 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!

image

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:

  1. You have created a valid pair of dice.
  2. You have added Dice through another initializer that has direct access to its properties.
  3. 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 ].

image

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 } // ["Sclip", "Nori"] 

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.

Source: https://habr.com/ru/post/352502/


All Articles