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ă.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

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
}