What’s New in Swift 4.2

Rahul Singh
Swift India
Published in
11 min readJul 7, 2018

Swift 4.2 is a major update from 4.1 and 4.0. The update was mostly related to improvements, code optimisation and runtime performance enhancements. You can now use Xcode 10.0 to build targets that are written in either Swift 4 or Swift 3. In this article, I would be discussing about the new features that were introduced in Swift 4.2. Lets get through these one by one.

Abolish ImplicitlyUnwrappedOptional (IUO) type — SE-0054

This proposal introduced the removal of ImplicitlyUnwrappedOptional type from the Swift and replaceing it with an IUO attribute on declarations. You would still continue to use the syntax T! . However, using ! at the end of a property or variable declaration's type no longer indicates that the declaration has IUO type. Rather, it indicates 2 thing:

  1. The declaration has optional type.
  2. The declaration has an attribute indicating that its value may be implicitly forced. (No human would ever write or observe this attribute, but we will refer to it as @_autounwrapped.) Such a declaration is referred to henceforth as an IUO declaration.

Below are some of the Examples:

// f: () -> Int?, has IUO attribute
func f() -> Int! {
return 3
}
// succeeds; x1: Int? = 3
let x1 = f()
// succeeds; x2: Int? = .some(3)
let x2: Int? = f()
// succeeds; x3: Int? = .some(3), has IUO attribute
let x3: Int! = f()
// succeeds; x4: Int = 3
let x4: Int = f()
// succeeds; a: [Int?] = [.some(3)]
let a1 = [f()]
// illegal, nested IUO type
let a2: [Int!] = [f()]
// succeeds; a: [Int] = [3]
let a3: [Int] = [f()]
// f: () -> Int?, has IUO attribute
func g() -> Int! {
return nil
}
// succeeds; y1: Int? = .none
let y1 = g()
// succeeds; y2: Int? = .none
let y2: Int? = g()
// succeeds; y3: Int? = .none, has IUO attribute
let y3: Int! = g()
// traps
let y4: Int = g()
// succeeds; b: [Int?] = [.none]
let b1 = [g()]
// illegal, nested IUO type
let b2: [Int!] = [g()]
// traps
let b3: [Int] = [g()]
func p<T>(x: T) {
print(x)
}
// prints “Optional(3)”; p is instantiated with T = Int?
p(f())
if let x5 = f() {
// executes, with x5: Int = 3
}
if let y5 = g() {
// does not execute
}

The proposal introduced would break the existing code under following 2 situations.

  1. Explicitly written nested IUO types (like [Int!]) should be changed to corresponding optional type ([Int?]) or non-optional type ([Int]) depending on context it is used.
  2. Variable bindings which previously had inferred type T! from their binding on the right-hand side will now have type T? . The compiler would display an error and suggest that the value be forced with! operator.

Conditional conformances — SE-0143

This was initially introduced in Swift 4.1 under SE-0185 and then redefined in Swift 4.2 to allow you to query at runtime. Swift 4.2 enabled synthesis of conditional conformances to Equatable and Hashable. Let us understand this with an example.

Suppose you have a protocol named TaxAmount:

protocol TaxAmount {
func taxAmount()
}

And a Struct that conforms to that protocol:

struct FoodItem: TaxAmount {
var rate = 1000.0
func taxAmount() {
let tax = rate/1.18
print(tax)
}
}

Then create an extension to Array conforming to TaxAmount . Also, have a method which would call the protocol method if it confirms to the desired Protocol.

extension Array: TaxAmount where Element: TaxAmount {
func taxAmount() {
for item in self {
item.taxAmount()
}
}
}
func calculateTax(_ value: Any) {
if let p = value as? TaxAmount {
p.taxAmount()
} else {
print("Not a Taxable Object")
}
}
calculateTax([FoodItem(), FoodItem(), FoodItem()])
// Prints 847.457627118644
//
Prints 847.457627118644
//
Prints 847.457627118644
calculateTax([1, 2, 3])
// Prints "Not a Taxable Object"

The if-let in calculateTax(_:) dynamically queries whether the type in value conforms to the protocol TaxAmount. In case of an Array, that conformance is conditional and requires another dynamic lookup to determine whether the element type conforms to TaxAmount. In the first case, the lookup finds the conformance of FoodItem to TaxAmount. In the second case, there is no conformance of Int to TaxAmount, so the conditional conformance fails.

The Conditional Conformance is not only limited to Array but is available to all the Types.

extension Optional: Equatable where Wrapped: Equatable {
/*== already exists */
}
extension Array: Equatable where Element: Equatable {
/*== already exists */
}
extension ArraySlice: Equatable where Element: Equatable {
/*== already exists */
}
extension ContiguousArray: Equatable where Element: Equatable {
/*== already exists */
}
extension Dictionary: Equatable where Value: Equatable {
/*== already exists */
}

In addition, conditional conformances can now also be implmented to Hashable for the above types, as well as for Range and ClosedRange.

extension Optional: Hashable where Wrapped: Hashable {
/*…*/
}
extension Array: Hashable where Element: Hashable {
/*…*/
}
extension ArraySlice: Hashable where Element: Hashable {
/*…*/
}
extension ContiguousArray: Hashable where Element: Hashable {
/*…*/
}
extension Dictionary: Hashable where Value: Hashable {
/*…*/
}
extension Range: Hashable where Bound: Hashable {
/*…*/
}
extension ClosedRange: Hashable where Bound: Hashable {
/*…*/
}

Derived Collection of Enum Cases — SE-0194

This proposal introduced a new CaseIterable protocol that would by default generate an array property of all cases in an enum. Prior to Swift 4.2, one would have to resort to various workarounds in order to iterate over all cases of a simple enum. But now, with Swift 4.2, you just have to conform to the CaseIterable protocol and complier would generate an allCases property that is an array of all your enum’s cases, in the order you defined them.

enum Direction: CaseIterable {
case east, west, north, south
}
for direction in Direction.allCases {
print("Directions are: \(direction).")
}

The automatic synthesis of allCases would only take place for enums that do not have associated values. You would need to implement it yourself in such cases.

enum Airport {
static var allCases: [Airport] {
return [.munich, .sanFrancisco, .singapore, .london(airportName: "Heathrow"), .london(airportName: "Gatwick")]
}
case munich
case sanFrancisco
case singapore
case london(airportName: String)
}

Introduce User-defined “Dynamic Member Lookup” Types — SE-0195

This proposal introduced a new @dynamicMemberLookup attribute. Types that use it, provide "dot" syntax for arbitrary names which are resolved at runtime - in a completely type safe way.

@dynamicMemberLookup
struct Person {
subscript(dynamicMember param: String) -> String {
let properties = ["personName": "John Appleseed", "City": "Ohio"]
return properties[param, default: ""]
}
}
let person = Person()
print(person.name)
print(person.city)
print(person.age)

In the above example, the Person object is created as it should be. Then it would print the person’s name and city as “John Appleseed” and “Ohio” respetively. Since, there is no property named “age”, it would return an empty string.subscript(dynamicMember:) method must return a string, which is where Swift’s type safety comes in. Even though you’re dealing with dynamic data, Swift will still ensure that you get back what you expected.

You can implement different subscript(dynamicMember:) methods for different return Data Types.

@dynamicMemberLookup
struct Person {
subscript(dynamicMember param: String) -> String {
let properties = ["personName": "John Appleseed", "City": "Ohio"]
return properties[param, default: ""]
}
subscript(dynamicMember param: String) -> Int {
let properties = ["age": 43, "height": 173]
return properties[member, default: 0]
}
}
let person = Person()
print(person.name)
// Prints "John Appleseed"
print(person.city)
// Prints "Ohio"
print(person.age)
// Prints 43
print(person.height)
// Prints 173

You can even overload subscript to return closures:

@dynamicMemberLookup
struct User {
subscript(dynamicMember parm: String) -> (_ title: String) -> Void {
return {
print("John Appleseed was born at \($0).")
}
}
}
let user = User()
user.print("Leominster, Massachusetts, United States")
// Prints "John Appleseed was born at Leominster, Massachusetts, United States"

@dynamicMemberLookup can be assigned to protocols, structs, enums, and even classes . One last example that I would like to cover in this would be the example given by the Auther (Chris Lattner). Its about JSON enum which uses dynamic member lookup to create more natural syntaxing. This example would realy help understand the use of @dynamicMemberLookup

enum JSON {
case IntValue(Int)
case StringValue(String)
case ArrayValue(Array<JSON>)
case DictionaryValue(Dictionary<String, JSON>)
}
extension JSON {
var stringValue : String? {
if case .StringValue(let str) = self {
return str
}
return nil
}
subscript(index: Int) -> JSON? {
if case .ArrayValue(let arr) = self {
return index < arr.count ? arr[index] : nil
}
return nil
}

subscript(key: String) -> JSON? {
if case .DictionaryValue(let dict) = self {
return dict[key]
}
return nil
}
subscript(dynamicMember param: String) -> JSON? {
if case .DictionaryValue(let dict) = self {
return dict[param]
}
return nil
}
}
let json = JSON.stringValue("Dynamic Member Lookup Example")

Below is the way of navigating an instance of JSON enum With and Without dynamic member look up.

Without dynamic member look up.
json[0]?["name"]?["first"]?.stringValue
With dynamic member look up.
json[0]?.name?.first?.stringValue

Compiler Diagnostic Directives — SE-0196

This proposal introduced #warning and #error directives that would prompt the Swift compiler to emit a custom warning or an error during compilation.

#warningForce Xcode to issue a warning when building your code.

func configPath() -> String {
#warning("This should be made more safe")
return Bundle.main().path(forResource: “Config”, ofType: “plist”)!
}

#errorGet comple time error and build would fail.

struct Configuration {
var apiKey: String {
#error("Please enter your API key below.")
return "Enter your key here"
}
}

NOTE: If a #warning or #error exists inside a branch of a #if directive that is not taken, then no diagnostic is emitted.

#if false
#warning("This will not trigger a warning.")
#error("This will not trigger an error.")
#endif
#if true
#warning("This will trigger a warning.")
#error("This will trigger an error.")
#endif

Adding in-place removeAll(where:) to the Standard Library — SE-0197

This proposal introduced a new removeAll(where:) method. This would remove all entities in a collection in-place matching a given predicate.

var name = ["John", "William", "Michael", "Gary", "Liam"]
name.removeAll { $0.hasSuffix("am") }
print(name)
// Prints "John", "Michael", "Gary"

If you where to implement this without removeAll(where:) method, then probably you would have used filter() method.

name = name.filter{!$0.hasPrefix("am")}

Adding toggle to Bool — SE-0199

This proposal introduced a new toggle() method to Booleans that would flip between true and false. This implemenation of this proposal is just few lines of code. It is easy and safe to write for complex data structures.

extension Bool {
mutating func toggle() {
self = !self
}
}
var loggedIn = false
loggedIn.toggle()

Random Unification — SE-0202

This proposal’s main focus was to create a unified random API, and a secure random API for all platforms. Currently, Swift used C API’s to perform random functionality. But with this, you can now, generate random numbers just by calling the random() method on whatever numeric type you want and providing the range you want to work with.

// Int
let randomIntFrom0To10 = Int.random(in: 0 ..< 10)
// Float
let
randomFloat = Float.random(in: 0 ..< 1)
// Double
let
randomDouble = Double.random(in: 0 .pi, using: &myCustomRandomNumberGenerator)
// Boolean
let
randomBool1 = Bool.random()
let randomBool2 = Bool.random(using: &myCustomRandomNumberGenerator)

Randomisation is just not limited to Numbers, but now, it can be used for Collections as well. Collection also has 2 new methods i.e.: shuffle() and shuffled() .

let greetings = ["hey", "hi", "hello", "hola"]// Utilizes the standard library’s default random. This returns an Optional
print(greetings.randomElement()!)
// This returns an Optional
print(greetings.randomElement(using: &myCustomRandomNumberGenerator)!)
var playingCards = ["Club", "Diamond", "Heart", "Spade"]// Shuffle in place
playingCards.shuffle()
// Get a shuffled array
let shuffled = playingCards.shuffled()

Add last(where:) and lastIndex(where:) Methods — SE-0204

This proposal primarily introduced 3 methods namely last(where:) lastIndex(where:) lastIndex(of:) . The first two requires a closure as an argument whereas the last requires the Element to be searched.

let numberSequence = [20, 30, 10, 40, 20, 30, 10, 40, 20]
let lastHighestNumber = numberSequence.last(where: { $0 > 25 })
print(lastHighestNumber)
// Prints 40
let lastHighestNumberIndex = numberSequence.lastIndex(where: { $0 > 25 })
print(lastHighestNumberIndex)
// Prints 7
let lastIndexOfElement = numberSequence.lastIndex(of: 10)
print(lastIndexOfElement)
// Prints 7

In addition, the index(of:) and index(where:) method names were also renamed to firstIndex(of:) and firstIndex(where:), respectively. This is beneficial for two reasons. First, it groups the first... and last... methods into two symmetric analogous groups. Second, it leaves the index... methods as solely responsible for index manipulation.

let firstHighestNumber = numberSequence.first(where: { $0 > 25 })
print(firstHighestNumber)
// Prints 30
let firstHighestNumberIndex = numberSequence.firstIndex(where: { $0 > 25 })
print(firstHighestNumberIndex)
// Prints 1
let firstIndexOfElement = numberSequence.firstIndex(of: 10)
print(firstIndexOfElement)
// Prints 2

Hashable Enhancements — SE-0206

This proposal introduced a new Hasher type representing the hash function. I extends the Hashable protocol with a new hash(into:) requirement that expresses hashing in terms of Hasher. This new requirement would replace the old hashValue property, which is now deprecated.

Before Swift 4.1, one had to calculate a hashValueproperty by themselves. In swift 4.1, this was improved so that hashValueproperty was synthesized automatically by the complier on Conformance. Hence, the below code

struct Person: Hashable {
var personName: String
var personAge: Int
var hashValue: Int {
return personName.hashValue ^ personAge.hashValue &* 16777619
}
}

turned into:

struct Person: Hashable {
var personName: String
var personAge: Int
}

However, if you want to implement your own Hashing mechanism, you would still need to implement a hashValue method using an algorithm that suits you best.

Swift 4.2 is improving this situation by introducing a new Hasher struct that provides a randomly seeded, universal hash(into:) function.

struct Person: Hashable {
var personName: String
var personAge: Int
func hash(into hasher: inout Hasher) {
hasher.combine(personName)
hasher.combine(personAge)
}
}

There is another way of doing this. You could also useHasher as a standalone hash generator.

let john = Person(personName: "John Appleseed", personAge: 50)
let paul = Person(personName: "Paul Warner", personAge: 45)
var hasher = Hasher()
hasher.combine(john)
hasher.combine(paul)
let hash = hasher.finalize()

You must not call finalize() if you’re writing a custom hash(into:) method for your types.

Hasher is a resilient struct, enabling future versions of the standard library to improve the hash function without the need to change (or even recompile) existing code that implements Hashable .

Add an allSatisfy algorithm to Sequence — SE-0207

This proposal introduced allSatisfy(_:) on Sequence that validated every element and returned true if they all match a given predicate:

let individualScores = [95, 96, 82, 87, 75]
let firstDivision = individualScores.allSatisfy { $0 >= 60 }
print(firstDivision)
// Prints true
let metricScore = [95, 96, 82, 87, 32]
let passed = metricScore.allSatisfy { $0 >= 40 }
print(passed)
// Prints false

Compiler Version Directive — SE-0212

This proposal introduced a compiler directive that is syntactically equivalent to the existing #if swift version check. This checks for the version of the compiler, regardless of which compatibility mode it is currently running.

Prior to Swift 4, the version of compiler and language were one and same. Swift 4 introduced a Compatibility Mode, where the compiler can also run for previous Swift versions. Below is the Table which shows the invocation of different Swift Version based on the Compiler and Language version.

Below are some of the example.

#if swift(>=4.1) && compiler(>=5.0)
print("Executes for Swift 5.0 compiler and above in — swift-version 4 mode and above.")
#endif
#if swift(>=4.1) || (swift(>=3.3) && !swift(>=4.0))
print("Executes for Swift 4.1 compiler and above.")
#endif
#if compiler(>=4.2)
print("Executes for Swift 4.2 compiler and above.")
#endif
#if compiler(>=5.0)
print("Executes for Swift 5.0 compiler and above.")
#endif

Points To Ponder:

  1. Calling off ImplicitlyUnwrappedOptional (IUO) type.
  2. Conditional conformances improvements.
  3. Iterating Enum cases.
  4. Dynamic member look up using dot syntax for subscripts.
  5. Warning and error diagnostic directives.
  6. In-built remove functionality based on certain conditions.
  7. Boolean toggling.
  8. Random number generation and shuffling.
  9. Method refactoring and adding new methods to Array.
  10. Hashable enhancements.
  11. Ability to check whether all elements in a Sequence satisfied a certain condition.
  12. Compiler version directive.

Hope you have enjoyed this. For further details (like Motivation, Design Solutions, Alternate Solution, Impacts, etc.) on any of the feature, click on the Link provided.

If you have any comment, question, or recommendation, feel free to post them in the comment section below! You can also follow me on Medium for new articles and connect with me on LinkedIn.

--

--