Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4370

What’s New in Swift 4.1?

$
0
0

Xcode 9.3 and Swift 4.1 are finally out of beta! This release contains some long-awaited improvements to the standard library and language itself. If you haven’t been following the Swift Evolution Process closely, keep reading.

In this tutorial, you’ll learn about the most significant changes introduced in Swift 4.1.

This article requires Xcode 9.3, so make sure you have it installed and ready to go before getting started.

Getting Started

Swift 4.1 is source-compatible with Swift 4, so the new features won’t break your code if you’ve already migrated your project to Swift 4 using the Swift Migrator in Xcode.

In the sections below, you’ll see linked tags such as [SE-0001]. These are Swift Evolution proposal numbers. I’ve included the link to each proposal so you can dig into the full details of each particular change. I recommend you try out the features in a playground so you have a better understanding of everything that changes as you work.

To start, fire up Xcode 9.3 and select File ▸ New ▸ Playground. Choose iOS as the platform and Blank as its template. Name and save it as you like. To get the most out of this tutorial, try out each feature in your new playground as you work.

Note: Need to catch up the highlights of Swift 4? No problem! Check out the predecessor to this tutorial, Swift 4: What’s New in Swift 4.

Language Improvements

There are a number of language improvements in this release, including conditional conformance, recursive constraints on associated types in protocols and more.

Conditional Conformance

Conditional conformance enables protocol conformance for generic types where the type arguments satisfy certain conditions [SE-0143]. This is a powerful feature that makes your code more flexible. You can see how it works with a few examples.

Conditional conformance in the standard library

In Swift 4, you could compare arrays, dictionaries and optionals as long as their elements were Equatable. This worked absolutely 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 test equality in these examples worked since Int is Equatable in Swift 4. However, comparing collections of optionals was a common situation you might have run into with Swift 4 since optionals do not conform to Equatable. Swift 4.1 fixes this issue using conditional conformance, letting optional types with underlying Equatable types to be compared:

// 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 has been solved when comparing arrays of arrays (e.g. [[Int]]). In Swift 4, you could only compare arrays of sets (e.g. [Set<Int>]), since sets conform to Equatable. Swift 4.1 solves this, since arrays (and dictionaries) are Equatable as long as their underlying values are, too.

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

Generally, Swift 4.1’s Optional, Array and Dictionary now conform to Equatable and Hashable whenever their underlying values or elements conform to these protocols.

This is how conditional conformance works in the standard library. Next, you will implement it in your own code.

Conditional conformance in code

You’re going to use conditional conformance to create your own band of musical instruments. Add the following block of code at the bottom of the playground to get started:

// 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’s what’s this does step-by-step:

  1. LeadInstrument conforms to Equatable. It has a certain brand and a method named tune() that you’ll eventually use to tune the instrument.
  2. You override tune() in Keyboard to return keyboard standard tuning.
  3. You do the same thing for Guitar.

Next, declare the band of instruments:

// 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’s what you’re doing step-by-step:

  1. You create a class called Band with a generic type – LeadInstrument. Each band has an unique name and lead instrument.
  2. You use where to constrain Band to conform to Equatable as long as LeadInstrument does. Your ability to conform the Band‘s generic LeadInstrument to Equatable is exactly where conditional conformance comes into play.

Next, define your favorite bands 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 piece of code, you create two Keyboards and Guitars along with their appropriate Bands. You then compare the bands directly, thanks to the conditional conformance you defined earlier.

Conditional conformance in JSON parsing

Arrays, dictionaries, sets and optionals conform to Codable if their elements conform to Codable in Swift 4.1. Add the following code to your playground to try this:

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)")
}

You use this code to encode [Student], [String: Student], Set<Student> and Student?. This works smoothly in Swift 4.1 since Student is Codable, which makes these collection types conform to it as well.

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!

Saving time with Swift 4.1!

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
}

Structure Initializers in Modules

Adding properties to public structs could lead to source-breaking changes in Swift 4. For this tutorial, make sure the Project Navigator is visible in Xcode by going to View\Navigators\Show Project Navigator. Next, right-click on Sources and select New File from the menu. Rename the file DiceKit.swift. Replace its contents with the following block of code:

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 struct's initializer makes sure both dice have valid values between 1 and 6. Switch back to the playground and add this code at the end of it:

// 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's what you did with this code:

  1. You created a valid pair of dice.
  2. You extended Dice with another initializer that has direct access to its properties.
  3. You defined an invalid pair of dice with the struct's new initializer.

In Swift 4.1, cross-target initializers should call the default one. Change your extension on Dice to:

extension Dice {
  init(_ firstValue: Int, _ secondValue: Int) {
    self.init(abs(firstValue - secondValue))
  }
}

This change makes structs behave like classes: cross-module initializers must be convenience initializers in Swift 4.1 [SE-0189].

In Swift 4.1 you can no longer cheat in dice games!

In Swift 4.1 you can no longer cheat in dice games!

Platform Settings and Build Configuration Updates

Swift 4.1 adds some much-needed platform and build features for code testing:

Build Imports

In Swift 4, you tested if a module was available on a certain platform by checking 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 succeeded. Swift 4.1 further simplifies this by letting you check for the module itself instead:

#if canImport(UIKit)
print("UIKit is available if this is printed!")
#endif

In Swift 4.1, you use #if canImport(UIKit) to confirm a certain framework is available for importing [SE-0075].

Target Environments

When writing Swift 4 code, the most well-known way to check whether you were running on a simulator or a physical device was by checking both the architecture and operation 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

If your architecture was Intel-based and your operating system was iOS, tvOS or watchOS, you were testing in the simulator. Otherwise, you were testing on the device.

This test was very cumbersome and was also very non-descriptive of the issue at hand. Swift 4.1 makes this test much more straightforward; just use targetEnvironment(simulator) [SE-0190] like so:

#if targetEnvironment(simulator)
  print("Testing in the simulator.")
#endif

Miscellaneous Bits and Pieces

There are a few other updates in Swift 4.1 that are worth knowing:

Compacting Sequences

In Swift 4, it was fairly common to use flatMap(_:) to filter out nil values from a sequence:

let pets = ["Sclip", nil, "Nori", nil]
let petNames = pets.flatMap { $0 } // ["Sclip", "Nori"]

Unfortunately, flatMap(_:) was overloaded in various ways and, in that specific scenario, the flatMap(_:) naming wasn't very descriptive of the action taken.

For these reasons, Swift 4.1 introduces a rename of flatMap(_:) to compactMap(_:) to make its meaning clearer and unique [SE-0187]:

let petNames = pets.compactMap { $0 }

Unsafe Pointers

Swift 4 used temporary unsafe mutable pointers to create and mutate unsafe mutable buffer 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 lets you work with unsafe mutable buffer pointers directly, using the same approach as with unsafe mutable pointers [SE-0184]:

let buffer = UnsafeMutableBufferPointer<Int>.allocate(capacity: 10)
let mutableBuffer = UnsafeMutableBufferPointer(mutating: UnsafeBufferPointer(buffer))

New Playground Features

Swift 4 allowed you to customize type descriptions in Xcode playgrounds:

class Tutorial {}
extension Tutorial: CustomPlaygroundQuickLookable {
  var customPlaygroundQuickLook: PlaygroundQuickLook {
    return .text("raywenderlich.com tutorial")
  }
}
let tutorial = Tutorial()

You implemented CustomPlaygroundQuickLookable for Tutorial to return a custom quick-look playground description. The description’s type in customPlaygroundQuickLook was limited to PlaygroundQuickLook cases. This is no longer the case (pun intended) in Swift 4.1:

extension Tutorial: CustomPlaygroundDisplayConvertible {
  var playgroundDescription: Any {
    return "raywenderlich.com tutorial"
  }
}

You implement CustomPlaygroundDisplayConvertible this time. The description’s type is Any now, so you can return anything from playgroundDescription. This simplifies your code and makes it more flexible [SE-0198].

Where to Go From Here?

You can download the final playground using the Download Materials link at either the top or bottom of this tutorial.

Swift 4.1 polishes up some Swift 4 features in preparation for more serious changes that will be coming in Swift 5 later this year. These include ABI stability, improved generics and strings, new memory ownership and concurrency models and more.

If you're feeling adventurous, head over and look at the Swift standard library diffs or to the official Swift CHANGELOG where you can read more information about all changes in this version. You can also use this to keep an eye out for what's coming in Swift 5!

If you're curious about what changes are coming in Swift 5 and beyond, we also recommend that you check out Swift Evolution proposals, where you can see which new features, changes and additions are being proposed. If you're really keen, why not give feedback on one of the current proposals under review or even pitch a proposal yourself!

What do you like or dislike about Swift 4.1 so far? Let us know in the forum discussion below!

The post What’s New in Swift 4.1? appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4370

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>