Aleksandr Pavliuk

Swift 4, Review the New - Part Two

On September 19, 2017, Apple announced release of Swift 4.0 on the official blog of Swift language. We've started discussing the changes it brought in Swift 4, Review the New - Part One. Let's see what else Swift 4.0 has for us.

 

1. Dictionary and Set Updates

Dictionary from Sequence

New Dictionary initializer from Sequence of key-value pairs added [SE-0165]:

let someNames = ["Ivan", "Gregor", "Kate"]

let newDictionary = Dictionary(uniqueKeysWithValues: zip(1..., someNames))

 

Merging Initializer and Merge Method

You can merge duplicate keys of Sequence while initializing Dictionary from it:

let duplicatedNames = [("Ivan", 1), ("Kate", 2), ("Ivan", 3), ("Kate", 4)]

let mergedNames = Dictionary(duplicatedNames, uniquingKeysWith: { (first, _) in first })

// ["Kate": 2, "Ivan": 1]

 

Merge two Dictionaries method:

let ukrainianNames = ["Ivan": true, "Masha": true, "Max": false]

var allAround = ["Kate": true, "Johnatan": true, "Max": true]

allAround.merge(ukrainianNames) { (old, _) in old }

// ["Ivan": true, "Johnatan": true, "Kate": true, "Max": true, "Masha": true]

 

Subscript with Default Value

You can specify a default value, it will return for missing keys of the subscript argument, important feature is that the returning type is non-optional.

newDictionary[5, default: "not found"]

 

Dictionary-Specific Map and Filter

filter and map methods are very powerful tools for developers. But it was awkward to have Array as a return type after applying this methods to Dictionary and Set. From now, filter for Dictionary returns Dictionary, and mapValues works similarly - keeps the dictionary structure during the change of values:

let filtered = newDictionary.filter {

   $0.key % 2 == 0

}

// [2: "Gregor"]

 

let mapped = newDictionary.mapValues { value in

   value.uppercased()

}

// [2: "GREGOR", 3: "KATE", 1: "IVAN"]

 

filter for Set returns a Set and not an Array:

let set: Set = [1,2,3,4,5]

let filteredSet = set.filter { $0 % 2 == 0 }

type(of: filteredSet) // Set<Int>

 

Grouping a Sequence

A very cool and useful feature is an ability to group sequences into buckets:

let myContacts = ["Tim", "Robert", "Tom", "Vinni"]

let groupedContacts = Dictionary(grouping: myContacts, by: { $0.first! })

groupedContacts // ["T": ["Tim", "Tom"], "R": ["Robert"], "V": ["Vinni"]]

 

2. MutableCollection.swapAt(_:_:)

In [SE-0173] introduced is a new swapAt method for MutableCollection, which does a very easy job: taking two indices and swapping appropriate elements:

var numbers = [1,2,3,4,5]

numbers.swapAt(0,1)

numbers // [2, 1, 3, 4, 5]

 

Unlike swap method, which manipulates inout arguments, the last one is marked as deprecated.

 

3. Reduce with inout

New variant of reduce function was added to the standard library. In this variant, partial result becomes inout, which is passed to the combine function as before [SE-0171]:

extension Sequence where Iterator.Element: Equatable {

   func uniq() -> [Element] {

       

       return reduce(into: []) { (result: inout [Element], element) in

           if !result.contains(element) {

               result.append(element)

           }

       }

   }

}

 

[1,2,3,2,4,6,1,6].uniq() // [1, 2, 3, 4, 6]

 

Motivation to do this is to make reduce faster. The previous version of reduce makes copies of result for each element of a sequence, preventing this leads to performance boost.

 

4. Generic Subscripts

Subscripts now can have generic arguments and a return type [SE-0148]. A good use case is to make a type that represents JSON. With a generic subscript you can define an expected return type:

struct JSON {

   fileprivate var storage: [String: Any]

   

   init(dictionary: [String: Any]) {

       self.storage = dictionary

   }

   

   subscript<T>(key: String) -> T? {

       return storage[key] as? T

   }

}

 

let json = JSON(dictionary: [

   "name": "Berlin",

   "country": "de",

   "population": 3_500_500

   ])

 

// No need to use as? Int

let population: Int? = json["population"]

 

5. Improved integers

[SE-0104] was originally accepted for Swift 3, but was implemented only in Swift 4 in a slightly revised version. As official documentation states “this proposal cleans up Swifts integer APIs and makes them more useful for generic programming.” The new protocols come into play once you want to write generic algorithms that work on multiple numeric types. You can now compare Int and UInt without explicit conversion:

let a: Int = 5

let b: UInt = 5

let c: Int8 = -10

a == b // doesn't compile in Swift 3

a > c  // doesn't compile in Swift 3

 

You can make arithmetic with overflow reporting:

let (partialValue, overflow) = Int32.max.addingReportingOverflow(1)

partialValue // -2147483648

overflow // true

 

And much more fun. Read the proposal [SE-0104] to get the full picture.

 

6. NSNumber Bridging

In Swift 3, some buggy behavior takes place during casting NSNumber to swift number types, fortunately this is fixed by [SE-0170]:

let n = NSNumber(value: UInt32(543))

let v = n as? Int8 // nil in Swift 4. This would be 31 in Swift 3.

 

7. Associated type constraints

The associated type can now be constrained with where clauses [SE-0142]:

protocol MyProtocol {

   associatedtype SpecialSequence: Sequence where SpecialSequence.Element: Equatable

}

 

8. Limiting @objc inference

Swift 4 no longer automatically infers a declaration to be @objc in many places like it was earlier. Now you'll need to use @objc explicitly in situations where you want the full dynamic dispatch capabilities of Objective-C [SE-0160].

@objcMembers

You can use a new @objcMembers attribute to class declaration if you want to expose all your classes to Objective-C.

Dynamic Doesn’t Imply @objc

Note that dynamic doesn’t imply @objc anymore, you need to add @objc dynamic to a declaration. It’s what you need to check in your projects during migration to new Swift.

 

9. Composing classes and protocols

Now you can create a variable of a specific type that is constrained to one or more protocols [SE-0156]:

class SomeClass {}

 

protocol Protocol1 {}

protocol Protocol2 {}

 

extension SomeClass: Protocol1 {}

extension SomeClass: Protocol2 {}

 

let variable: SomeClass & Protocol1 & Protocol2 = SomeClass()

 

 

Conclusion

Presented in Swift 4, Review the New - Part One and here are all changes that Swift 4 presents. Swift becomes more and more friendly and pretty looking. More information you can find at these beautiful resources:

https://github.com/ole/whats-new-in-swift-4

https://developer.apple.com/videos/play/wwdc2017/402/

Let’s make fun with Swift :)

Aleksandr
Pavliuk
Subscribe for regular updates
By clicking Submit you agree to Sigma Software's Privacy Policy