Swift · · 21 min read

What's New in Swift 4 by Example

What's New in Swift 4 by Example

Apple announced Swift 4 as part of Xcode 9 at WWDC a few weeks ago. Although still in beta during the summer until its final release in September, this is the first version of the language that doesn’t break your code. It brings some really nice improvements and very much needed additions to existing Swift 3 features which you will learn all about in this article. So without any further ado, let’s dive in! 🙂

This article assumes that you already have some working knowledge of Swift 3. If you need a quick reminder of the most important Swift 3 and Swift 3.1 highlights and changes, check out my what’s new in Swift 3 and Swift 3.1 articles.

I will use Playground to go through the code change with you. If you want to fully understand the new features/changes of Swift 4, I suggest you to open Xcode 9 beta and create a new Playground file. Some of the examples in this article require the Foundation framework in order to work properly, so go ahead and import it before we begin anything if you want to try everything in a playground while reading:

import Foundation

JSON Encoding and Decoding

Let me begin by showing you one of the cool features in the new version of Swift. Swift 4 simplifies the whole JSON archival and serialization process you were used to in Swift 3. Now you only have to make your custom types implement the Codable protocol – which combines both the Encodable and Decodable ones – and you’re good to go:

class Tutorial: Codable {
  let title: String
  let author: String
  let editor: String
  let type: String
  let publishDate: Date
  
  init(title: String, author: String, editor: String, type: String, publishDate: Date) {
    self.title = title
    self.author = author
    self.editor = editor
    self.type = type
    self.publishDate = publishDate
  }
}

let tutorial = Tutorial(title: "What's New in Swift 4?", author: "Cosmin Pupăză", editor: "Simon Ng", type: "Swift", publishDate: Date())

Your Tutorial class conforms to the Codable protocol, so let’s go ahead and encode your tutorial object:

let encoder = JSONEncoder()
let data = try encoder.encode(tutorial)
let string = String(data: data, encoding: .utf8)

You first create an encoder object with the JSONEncoder class designated initializer. Then you archive the tutorial into a data object using the try statement and the encoder’s encode(_:) method. Finally, you convert the data to a string using UTF-8 encoding. If you follow me using Playground, you will see the JSON output:

swift-json

Now let’s see how to get back the initial tutorial object:

let decoder = JSONDecoder()
let article = try decoder.decode(Tutorial.self, from: data)
let info = "\(article.title) \(article.author) \(article.editor) \(article.type) \(article.publishDate)"

First of all you declare the decoder object with the JSONDecoder class initializer. Then you decode the encoded data inside a try block like before with the decoder’s decode(_: from:) method. Last but not least, you use string interpolation to generate a custom string using the article object’s properties. Now it’s so simple to decode JSON data.

swift-json-decode

Note: You can read more about this change here and here.

Smarter Key Paths

Swift 4 makes it easier to access an object’s properties with key paths. Consider the following class implementation:

class Author {
  let name: String
  let tutorial: Tutorial
  
  init(name: String, tutorial: Tutorial) {
    self.name = name
    self.tutorial = tutorial
  }
}
let author = Author(name: "Cosmin Pupăză", tutorial: tutorial)

You use the previously created Tutorial class in order to define a certain tutorial for the author object. Now here’s how you get the author’s name using key paths:

let authorNameKeyPath = \Author.name
let authorName = author[keyPath: authorNameKeyPath]

You first create a key path with a backslash and then use the keyPath subscript to get the author object’s name.

But that’s not all – you may use key paths to go through the whole class hierarchy and determine the tutorial object’s title like this:

let authorTutorialTitleKeyPath = \Author.tutorial.title
let authorTutorialTitle = author[keyPath: authorTutorialTitleKeyPath]

You can even add a new key path to an already defined one using the key path’s appending(path:) method as follows:

let authorTutorialKeyPath = \Author.tutorial
let authorTutorialNameKeyPath = authorTutorialKeyPath.appending(path: \.title)
let authorTutorialName = author[keyPath: authorTutorialNameKeyPath]

I’ve left the best part at the end – using key paths in order to change property values:

class JukeBox {
  var song: String
  
  init(song: String) {
    self.song = song
  }
}

let jukeBox = JukeBox(song: "Nothing else matters")
let jukeBoxSongKeyPath = \JukeBox.song
jukeBox[keyPath: jukeBoxSongKeyPath] = "Stairway to heaven”

You first define the JukeBox class and create a jukeBox object with a new song to play. Then you define a key path for the song property and use it in order to change the song.

Note: You can read more about this proposal here.

Mixing Classes with Protocols

You can combine protocols together in Swift 3 when creating constants and variables. Swift 4 goes one step further and lets you add classes to the mix using the same syntax. You may constrain a certain object to a class and a protocol in just one go the same way as in Objective-C.

Imagine that you are coding a drawing and colouring app. The main classes and protocols prototypes that you are using look like this:

protocol Drawable {}
protocol Colourable {}

class Shape {}
class Line {}

You create two base classes for shapes and lines and two protocols that handle both of the drawing and colouring corresponding logic. Now suppose that you want to define some specific types for your app:

class Circle: Shape, Drawable, Colourable {}
class Rectangle: Shape, Drawable, Colourable {}
class Square: Shape, Drawable, Colourable {}

class StraightLine: Line, Drawable {}
class DottedLine: Line, Drawable {}

let circle: Circle
let rectangle: Rectangle
let square: Square

let straightLine: StraightLine
let dottedLine: DottedLine

First, you create three concrete classes: Circle, Rectangle and Square which inherit from the Shape base class and conform to both the Drawable and Colourable protocols. Then you declare two derived classes: StraightLine and DottedLine that inherit the Line class and implement the Drawable and Colourable protocols as well. Finally, you define objects for each and every single class you have previously created.

The problem with this approach is that it complicates the class hierarchy by making way too many subclasses for each new type in your app. You could go ahead and simplify things a bit by combining the two protocols together:

let newCircle: Drawable & Colourable
let newRectangle: Drawable & Colourable
let newSquare: Drawable & Colourable

You make your objects adhere to both the Drawable and Colourable protocols with the & operator. You may go even one step further and simplify the syntax a little bit more by creating a type alias for your protocols:

typealias DrawableColourable = Drawable & Colourable

let anotherCircle: DrawableColourable
let anotherRectangle: DrawableColourable
let anotherSquare: DrawableColourable

Your objects conform to the DrawableColourable compound protocol, but it’s not enough because the initial problem still remains unsolved. Swift 4 to the rescue – add the base classes to the mix like this:

let brandNewCircle: Shape & Drawable & Colourable
let brandNewRectangle: Shape & Drawable & Colourable

let brandNewSquare: Shape & Drawable & Colourable
let brandNewStraightLine: Line & Drawable
let brandNewDottedLine: Line & Drawable

You create Shape and Line objects and make them implement their corresponding Drawable and Colourable protocols with the & operator. Your class hierarchy is short and sweet now and guess what – you may also create a type alias for the whole thing in order to simplify the syntax:

typealias DrawableColourableShape = Shape & Drawable & Colourable
typealias DrawableLine = Line & Drawable

let anotherNewCircle: DrawableColourableShape
let anotherNewRectangle: DrawableColourableShape

let anotherNewSquare: DrawableColourableShape
let anotherNewStraightLine: DrawableLine
let anotherNewDottedLine: DrawableLine

You define type aliases for the DrawableColourableShape and DrawableLine custom types and declare corresponding objects for them. Your drawing and colouring app design looks just great now – way to go! 🙂

Further Reading: You can read more about this proposal over here.

One Sided Ranges

Swift 4 adds prefix and postfix versions for the half open range and closed range operators in order to create one sided ranges. Here is how you get the first half of a given array in Swift 3:

let array = [1, 5, 2, 8, 4, 10]
let halfIndex = (array.count - 1) / 2
let openFirstHalf = array[0..<halfIndex]
let closedFirstHalf = array[0...halfIndex]

You use the half open range and closed range operator to get the corresponding open and closed first half of the array. Both ranges start with the 0 index since you are beginning with the very first element of the array, so you can safely infer it without explicitly mentioning it in Swift 4 as follows:

let openFirstSlice = array[..<halfIndex]
let closedFirstSlice = array[...halfIndex]

You drop the range’s start index because you process all of the array’s elements up to the halfIndex corresponding one. Now let’s see how to return the second half of the array in Swift 3:

let nextIndex = halfIndex + 1
let lastIndex = array.count - 1
let openSecondHalf = array[nextIndex..<lastIndex + 1]
let closedSecondHalf = array[nextIndex...lastIndex]

Nothing new here – you use the half open range and closed range operators to go from where you left last time to the end of the array. Just like in the previous case, you don’t need to mention the array’s last element corresponding index for closed ranges in Swift 4:

let closedSecondSlice = array[nextIndex...]

You drop the range’s end index since you go from the nextIndex corresponding array element right to the very end of the array.

The one sided range operators let you create infinite sequences that start with any given value. This is how you display the array’s indexes and values in Swift 3:

for (index, value) in array.enumerated() {
  print("\(index + 1): \(value)")
}

You print a tuple for each and every index and its corresponding value using a for in loop and the array’s enumerated() method. The array’s first index is always 0, so you should increase it by 1 in order to start with 1 instead. This isn’t necessary anymore in Swift 4:

for (index, value) in zip(1..., array) {
  print("\(index): \(value)")
}

You use the zip(_: _:) function inside a for in loop in order to combine the one sided range of indexes with the array’s values – the first index is 1 now. Notice that although the index sequence is infinite, the loop stops when all of the array’s elements have been printed. This is the only way of avoiding to create an endless loop in this case: you should terminate it at some point so that it doesn’t go on and on like crazy.

Note: You can’t loop through a one sided range which omits its first value, because you will never know at which index should the iteration begin after all.

One sided ranges work really well with pattern matching techniques in switch statements. There is only one caveat though – you must add a default case in order to make the switch exhaustive since ranges are infinite now:

let favouriteNumber = 10
switch favouriteNumber {
  case ..<0:
    print("Your favourite number is a negative one.")
  case 0...:
    print("Your favourite number is a positive one.")
  default:
    break
}

You use infinite ranges to test if your favourite whole number is either positive or negative by comparing it to 0. The ..<0 and 0... patterns model their corresponding (-∞, 0) and [0, +∞) intervals. Notice that the default case is never used here, so you just bail out of it with the break statement.

Note: You can read more about the proposal of this change over here.

swap vs swapAt

The swap(_:_:) mutating method in Swift 3 takes two elements of a certain array and swaps them on the spot:

var numbers = [1, 5, 2, 8, 4, 10]
swap(&numbers[0], &numbers[1])

This solution has one major drawback: the swapped elements are passed to the function as inout parameters so that it can access them directly. Swift 4 takes a totally different approach by replacing the method with a swapAt(_:_:) one which takes the two elements corresponding indices and swaps them just as before:

numbers.swapAt(0, 1)

The swap(_:_:) function can also swap two given values in Swift 3 – you pass the values as inout arguments like you did in the previous case:

var a = 1
var b = 2

swap(&a, &b)

The swap(_:_:) function will be deprecated and completely removed in Swift 4, so you have two ways to replace it. The first approach uses another constant to perform the actual swap:

let aux = a
a = b
b = aux 

The second solution takes the advantage of Swift built in tuples to implement the previous algorithm with only one line of code:

(b, a) = (a, b)
Note: You can read more about this proposal here.

Improved Dictionaries and Sets

Dictionaries and sets are two useful data structures, but they have always lacked some important features in Swift 3, so Swift 4 improves them both. Let’s start with dictionaries since there are more changes there than in the case of sets.

First of all, here’s how you create a dictionary from an array of tuples in Swift 4:

let tupleArray = [("Monday", 30),  ("Tuesday", 25),  ("Wednesday", 27),  ("Thursday", 20),  ("Friday", 24),  ("Saturday", 22),  ("Sunday", 26)]
let dictionary = Dictionary(uniqueKeysWithValues: tupleArray)

You use the dictionary’s init(uniqueKeysWithValues:) initializer to create a brand new dictionary from a tuples array. Each tuple represents the average temperature of a certain day of the week, so the dictionary’s keys are the names of the days and its values are their corresponding temperatures.

Note: Temperatures are measured in Celsius degrees and the first day of the week is Monday where I come from. Feel free to use Fahrenheit degrees and start with Sunday if needed.

If you have different arrays for the dictionary’s keys and values to begin with, you can still build the whole thing like this:

let keys = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
let values = [30, 25, 27, 20, 24, 22, 26]
let newDictionary = Dictionary(uniqueKeysWithValues: zip(keys, values))

The zip(_:_:) function creates the previous array of tuples from the corresponding arrays. But what if you have only the values array and no keys instead? One sided ranges to the rescue all over again:

let anotherDictionary = Dictionary(uniqueKeysWithValues: zip(1..., values))

You create an infinite sequence that starts with 1 and combine it with the dictionary’s values in the zip(_:_:) function: the keys go now from 1 for Monday to 7 for Sunday – good job! 🙂

But what about duplicates? Swift 4 is smart enough to let you handle by yourself any duplicate values the dictionary might have like this:

let duplicatesArray = [("Monday", 30),  ("Tuesday", 25),  ("Wednesday", 27), ("Thursday", 20),  ("Friday",   24),  ("Saturday", 22),  ("Sunday", 26),  ("Monday", 28)]
let noDuplicatesDictionary = Dictionary(duplicatesArray, uniquingKeysWith: min) 

You create a dictionary which has only unique keys with the dictionary’s init(_: uniquingKeysWith:) initializer. It uses a specific algorithm to choose the corresponding value for each and every duplicate key in the dictionary. In this case, if there are two different temperatures for a certain day of the week, you go with the smallest one of them.

But what if you want to merge some duplicates to an existing dictionary? Swift 4 has got you covered here as well:

let duplicateTuples = [("Monday", 28),  ("Tuesday", 27)]
var mutatingDictionary = Dictionary(uniqueKeysWithValues: tupleArray)
mutatingDictionary.merge(duplicateTuples, uniquingKeysWith: min)
let updatedDictionary = mutatingDictionary.merging(duplicateTuples, uniquingKeysWith: min)

Adding elements to a dictionary comes in two flavours: you can either modify the original dictionary on the spot with the mutating merge(_: uniquingKeysWith:) method or create a completely new dictionary from scratch with the non-mutating merging(_: uniquingKeysWith:) one. The duplicates filtering algorithm works as in the previous case – feel free to try anything else and see what happens.

Dictionary subscripts in Swift 3 return the value of a corresponding key as an optional, so you use the nil coalescing operator to set default values for missing keys:

var seasons = ["Spring" : 20, "Summer" : 30, "Autumn" : 10]
let winterTemperature = seasons["Winter"] ?? 0

The requested season isn’t in the dictionary, so you set a default temperature for it. Swift 4 adds a brand new subscript for specific values of keys that don’t exist in the dictionary, so you don’t need the nil coalescing operator anymore in this case:

let winterTemperature = seasons["Winter", default: 0]

The custom subscript lets you update values of existing keys more easily. Here’s how you would do this in Swift 3 without it:

if let autumnTemperature = seasons["Autumn"] {
  seasons["Autumn"] = autumnTemperature + 5
}

You first have to unwrap the optional value of the corresponding season’s name with the if let statement and then update it if it’s not nil. Now let’s see how it’s all done in Swift 4 with the default value subscript:

seasons["Autumn", default: 0] += 5

You combine the new subscript with the addition assignment operator in order to solve the task in just one line of code – now isn’t that something or what? 🙂

Both map(_:) and filter(_:) have been redesigned for dictionaries in Swift 4 because they return arrays, not dictionaries in Swift 3. For example, this is how you map the values of a given dictionary in Swift 3:

let mappedArrayValues = seasons.map{$0.value * 2}

You map the temperatures for the seasons dictionary from Celsius degrees to Fahrenheit ones using shorthand arguments. If you want to map the keys as well, you would do it like this in Swift 3:

let mappedArray = seasons.map{key, value in (key, value * 2)}

This approach creates an array of tuples out of the dictionary’s elements but it won’t work in Swift 4 because you can’t do tuple destructuring for closure parameters any longer (more about that in another section of the article). The first workaround uses the whole tuple as the map(_:) function’s argument:

let mappedArray = seasons.map{season in (season.key, season.value * 2)}

The second solution uses shorthand syntax instead:

let mappedArrayShorthandVersion = seasons.map{($0.0, $0.1 * 2)}

Both versions are OK overall, but you are still stuck with arrays instead of dictionaries after all, so let’s go ahead and fix this:

let mappedDictionary = seasons.mapValues{$0 * 2}

You map the original dictionary into a brand new dictionary which has the same structure as the initial one with the mapValues(_:) function – how cool is that? 🙂

Now here’s how you filter the values of a dictionary in Swift 3 with shorthand syntax:

let filteredArray = seasons.filter{$0.value > 15}

Only the temperatures above 15 make it into the filtered array. But what if we want a filtered dictionary instead? Swift 4 to the rescue once more:

let filteredDictionary = seasons.filter{$0.value > 15}

The code is exactly the same as in the previous case, but you have a filtered dictionary with the same structure as the original one this time – sweet! 🙂

Note: You can use the type(of:) function in order to determine the actual type of the mapped and filtered arrays and dictionaries. If you want to learn more about functional programming techniques, check out my map tutorial and my filter one.

Swift 4 enables you to split a dictionary into groups of values like this:

let scores = [7, 20, 5, 30, 100, 40, 200]
let groupedDictionary = Dictionary(grouping: scores, by:{String($0).count})

The dictionary’s init(grouping: by:) initialiser groups together the dictionary’s values by specific keys. In this case, you partition the scores array by the number of digits for each particular score like this:

[ 1: [7, 5], 2: [20, 30, 40], 3: [100, 200] ]

You may also rewrite the whole thing using trailing closure syntax as follows:

let groupedDictionaryTrailingClosure = Dictionary(grouping: scores) {String($0).count}

You extract the closure for the dictionary’s key out of the initializer because the closure is the initializer’s last argument in this case – really cool! 🙂

Swift 3 lets you create a dictionary with an initial capacity like this:

let ratings: Dictionary = Dictionary(minimumCapacity: 10)

You can store at least ten ratings in the ratings dictionary with the dictionary’s init(minimumCapacity:) initializer, but there’s no way of checking out for the current capacity or reserving more storage. Swift 4 saves the day once more:

seasons.capacity
seasons.reserveCapacity(4)

You get the dictionary’s current storage with the capacity property and create memory in order to store more elements with the reserveCapacity(_:) method.

That’s all about dictionaries. Both sets and dictionaries are collections under the hood, so let’s see what dictionary changes apply to sets as well.

Sets are a special case of arrays because they store only unique items. However, when you filter a set in Swift 3 you get an array, not a set:

var categories: Set = ["Swift", "iOS", "macOS", "watchOS", "tvOS"]
let filteredCategories = categories.filter{$0.hasSuffix("OS")}

You filter the categories set in order to get only the Apple technologies that are related to operating systems. Swift 4 returns a filtered set instead:

let filteredCategories = categories.filter{$0.hasSuffix("OS")}

The code is the same as in Swift 3, but the operating systems are stored in a set instead of an array this time.

You may create a set with a default capacity in Swift 3 like this:

let movies: Set = Set(minimumCapacity: 10)

The movies set stores a minimum of ten movies to start with. Swift 4 takes everything to the next level by declaring the set’s capacity and defining extra space for its items:

categories.capacity
categories.reserveCapacity(10)

You can store more categories in the set and access its capacity anytime. That’s it for sets! 🙂

Note: You can read more about this change here.

Private vs Fileprivate in Extensions

Swift 3 uses the fileprivate access control modifier in order to make the important data of a certain class available anywhere outside of the class as long as you actually access it from the very same file. This is how it all works in the case of extensions:

class Person {
  fileprivate let name: String
  fileprivate let age: Int
  
  init(name: String, age: Int) {
    self.name = name
    self.age = age
  }
}

extension Person {
  func info() -> String {
    return "\(self.name) \(self.age)"
  }
}

let me = Person(name: "Cosmin", age: 31)
me.info()

You create an extension for the Person class and use string interpolation to access its private properties in the info() method. This is such a common pattern in Swift, so you can now use the private access level instead of the fileprivate one in Swift 4 in order to access class properties in an extension as if you would do it inside the class itself:

class Person {
  private let name: String
  private let age: Int
  // original code
}
Note: You can read more about this proposal here.

NSNumber Improvements

Swift 3 doesn’t typecast an NSNumber to an UInt8 properly:

let x = NSNumber(value: 1000)
let y = x as? UInt8

The right value of y should be nil in this case because the Uint8 type begins with 0 and goes up to 255 only. Swift 3 takes a totally different approach by computing the remainder instead: 1000 % 256 = 232. This weird behaviour has been fixed in Swift 4, so y is nil now as you would expect in the first place.

Note: You can read more about this change here.

Unicode Scalars for Characters

You can’t access the unicode scalars of a certain character directly in Swift 3 – you have to convert it to a string first like this:

let character: Character = "A"
let string = String(character)
let unicodeScalars = string.unicodeScalars
let startIndex = unicodeScalars.startIndex
let asciiCode = unicodeScalars[startIndex].value

You determine the character’s ASCII code from the unicode scalars of the corresponding string. Swift 4 adds a unicodeScalars property to characters, so you can access it directly now:

let unicodeScalars = character.unicodeScalars
// original code
Note: You can read more about this proposal here.

Improved Emoji Strings

Swift 3 doesn’t determine the number of characters in emoji strings correctly:

let emojiString = "👨‍👩‍👧‍👦"
let characterCountSwift3 = emojiString.characters.count

The number of characters for the string should be 1 because there is only one emoji character in there, but Swift 3 returns 4 in this case. Swift 4 fixes this:

let characterCountSwift4 = emojiString.count

As you can see, you can access the count property directly on the string itself because strings are treated as collections in Swift 4 – more about that in another section of the article.

Multiple Line Strings

You declare strings on multiple lines in Swift 3 by adding \n symbols for each and every new line and escaping all double quotes in the text like this:

let multipleLinesSwift3 = "You can \"escape\" all of the double quotes within the text \n by formatting it with a \"\\\" backslash before each and every single double quote that appears out there \n and insert inline \\n symbols before each and every new line of text \n in order to display the string on multiple lines in Swift 3.”

Swift 4 takes a completely different approach for multiple line strings by using triple quotes instead, so you don’t have to escape double quotes anymore:

let multiLineStringSwift4 = """
You can display strings
on multiple lines by placing a
\""" delimiter on a separate line
both right at the beginning
and exactly at the very
end of it
and don't have to "escape" double quotes
"" in Swift 4.
"""
Note: You can read more about this change here.

Generic Subscripts

Swift 4 lets you create generic subscripts: both the subscript’s parameters and return type may be generic. Here’s a non-generic Swift 3 subscript that determines the maximum and minimum values in a given array:

// 1
extension Array where Element: Comparable {
  // 2
  subscript(minValue: Element, maxValue: Element) -> [Element] {
    // 3
    var array: [Element] = []
    // 4
    if let minimum = self.min(), minimum == minValue {
      // 5
      array.append(minValue)
    }
    // 6 
    if let maximum = self.max(), maximum == maxValue {
      array.append(maxValue)
    }
    // 7
    return array
  }
}

This is what’s happening over here, step by step:

  1. You create an extension on the Array class and use the where keyword in order to constrain its elements to implement the Comparable protocol.
  2. The subscript takes two parameters which are of the same type as the array’s items and returns an array created from its corresponding arguments.
  3. You define the final array which you will return from the subscript later on – it’s empty to begin with.
  4. You unwrap the original array’s minimum value inside an if let statement and check if it’s equal to the subscript’s first parameter.
  5. You add the minimum value to the final array declared earlier if the previous test succeeds.
  6. You repeat steps 4 and 5 for the initial array’s maximum value and the subscript’s second parameter this time.
  7. You return the final array and you’re done.

Now here’s how the subscript looks like in action for arrays of numbers and strings:

let primeNumbers = [3, 7, 5, 19, 11, 13]

let noMinOrMaxNumber = primeNumbers[5, 11] // []
let onlyMinNumber = primeNumbers[3, 13] // [3]
let justMaxNumber = primeNumbers[7, 19] // [19]
let bothMinAndMaxNumbers = primeNumbers[3, 19] // [3, 19]

let greetings = ["Hello", "Hey", "Hi", "Goodbye", "Bye"]

let noFirstOrLastGreeting = greetings["Hello", "Goodbye"] // []
let onlyFirstGreeting = greetings["Bye", "Hey"] // ["Bye"]
let onlyLastGreeting = greetings["Goodbye", "Hi"] // ["Hi"]
let bothFirstAndLastGreeting = greetings["Bye", "Hi"] // ["Bye", "Hi"]

That’s pretty cool, but you can also make it work for sequences in Swift 4 like this:

extension Array where Element: Comparable {
  // 1
  subscript(type: String, sequence: T) -> [T.Element] where T.Element: Equatable {
    // 2
    var array: [T.Element] = []
    // 3
    if let minimum = self.min(), let genericMinimum = minimum as? T.Element, sequence.contains(genericMinimum) {
      array.append(genericMinimum)
    }
    if let maximum = self.max(), let genericMaximum = maximum as? T.Element, sequence.contains(genericMaximum) {
      array.append(genericMaximum)
    }
    
   return array
  }
}

Let’s go over what’s changed since last time:

  1. The subscript is generic now. It takes two parameters: a generic sequence and the sequence’s type as a string and returns a generic array: its elements must conform to the Equatable protocol.
  2. The final array is generic in this case as well.
  3. You create the generic versions of the array’s unwrapped minimum and maximum values and check if the sequence contains both of them.

The subscript is really powerful now – this is how you use it with specific sequences like arrays and sets of numbers and strings:

let noMinOrMaxArray = [5, 11, 23]
let numberFirstArray = primeNumbers["array", noMinOrMaxArray] // []

let onlyMinNumberSet: Set = [3, 13, 2, 7]
let numberFirstSet = primeNumbers["set", onlyMinNumberSet] // [3]

let justMaxNumberArray = [7, 19, 29, 10]
let numberSecondArray = primeNumbers["array", justMaxNumberArray] // [19]

let bothMinAndMaxSet: Set = [3, 17, 19]
let numberSecondSet = primeNumbers["set", bothMinAndMaxSet] // [3, 19]

let noFirstOrLastArray = ["Hello", "Goodbye"]
let stringFirstArray = greetings["array", noFirstOrLastArray] // []

let onlyFirstSet: Set = ["Bye", "Hey", "See you"]
let stringFirstSet = greetings["set", onlyFirstSet] // ["Bye"]

let onlyLastArray = ["Goodbye", "Hi", "What's up?"]
let stringSecondArray = greetings["array", onlyLastArray] // ["Hi"]

let bothFirstAndLastSet: Set = ["Bye", "Hi"]
let stringSecondSet = greetings["set", bothFirstAndLastSet] // ["Bye", "Hi"]
Note: You can read more about this proposal here.

Strings as Collections

Strings are collections in Swift 4, so you can now treat them like arrays or sequences and simplify certain tasks. For example, this is how you would filter a given string in Swift 3:

let swift3String = "Swift 3"
var filteredSwift3String = ""

for character in swift3String.characters {
  let string = String(character)
  let number = Int(string)
  
  if number == nil {
    filteredSwift3String.append(character)
  }
}

You use a for in loop to process the string’s characters one by one. Each character is first converted to a string and then you check if the corresponding string isn’t actually a number under the hood. Finally, you add the corresponding character to the filtered string if the previous test succeeds.

Swift 4 lets you do all this by using functional programming techniques directly on the string itself instead:

let swift4String = "Swift 4”
let filteredSwift4String = swift4String.filter{Int(String($0)) == nil}

Swift 3 returns a string when defining substrings from a given string:

let swift3SpaceIndex = swift3String.characters.index(of: " ")
let swift3Substring = swift3String.substring(to: swift3SpaceIndex!)

You get the corresponding substring with the string’s substring(to:) method. Swift 4 takes a completely different approach by adding a brand new Substring type to the mix:

let swift4SpaceIndex = swift4String.index(of: " ")
let swift4Substring = swift4String[..<swift4SpaceIndex!]

You use one sided ranges to determine the substring in this case – its type is Substring, not String this time. Both the String and Substring types conform to the StringProtocol protocol, so they behave exactly the same.

Note: You can read more about this change here.

Improved Objective-C Inference

Swift 3 automatically infers the @objc annotation for selectors and key paths that use properties or methods which are also available in Objective-C:

class ObjectiveCClass: NSObject {
   let objectiveCProperty: NSObject
   func objectiveCMethod() {}
  
  init(objectiveCProperty: NSObject) {
    self.objectiveCProperty = objectiveCProperty
  }
}

let selector = #selector(ObjectiveCClass.objectiveCMethod)
let keyPath = #keyPath(ObjectiveCClass.objectiveCProperty)

You create a custom class that inherits from NSObject and define selectors and key paths for its properties and methods. Swift 4 requires you to manually add the previous annotation yourself so that everything still works correctly:

class ObjectiveCClass: NSObject {
 @objc let objectiveCProperty: NSObject
 @objc func objectiveCMethod() {}
 // original code
}
Note: You can read more about this proposal here.

Tuple Destructuring in Closures

Swift 3 lets you work directly with a tuple’s components if they are used as parameters in a given closure. Here’s how you implement this feature in order to map an array of tuples using functional programming techniques:

let players = [(name: "Cristiano Ronaldo", team: "Real Madrid"), (name: "Lionel Messi", team: "FC Barcelona")]
let tupleDestructuring = players.map{name, team in (name.uppercased(), team.uppercased())}

You make use of the closure’s arguments in order to uppercase the player’s name and team. This isn’t possible anymore in Swift 4, but there are two workarounds instead. The first one uses the whole tuple for the closure’s parameter like this:

let tuple = players.map{player in (player.name.uppercased(), player.team.uppercased())}

The second solution uses shorthand arguments for brevity:

let shorthandArguments = players.map{($0.0.uppercased(), $0.1.uppercased())}
Note: This change has generated a lot of debate in the Swift community and will most likely be reverted in the future, but it’s here to stay for now. You can read more about it here.

Constrained Associated Types in Protocols

You may constrain associated types inside protocols in Swift 4:

protocol SequenceProtocol {
  associatedtype SpecialSequence: Sequence where SpecialSequence.Element: Equatable
}

First you create a custom protocol and you add an associated type to it that conforms to the Sequence protocol. Then you use a where clause in order to constrain the elements of the sequence: they should all implement the Equatable protocol.

Note: You can read more about this proposal here.

Conclusion

That’s all about Swift 4. I hope you like this tutorial and enjoy all the changes in Swift 4. In the meantime, please let me know if you have any questions or issues. Happy Swifting! 🙂

Read next