What’s New in Swift 4.2
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:
- The declaration has optional type.
- 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.
- 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. - Variable bindings which previously had inferred type
T!
from their binding on the right-hand side will now have typeT?
. 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.457627118644calculateTax([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 43print(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"]?.stringValueWith 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.
#warning
— Force 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”)!
}
#error
— Get 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 40let lastHighestNumberIndex = numberSequence.lastIndex(where: { $0 > 25 })
print(lastHighestNumberIndex)
// Prints 7let 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 30let firstHighestNumberIndex = numberSequence.firstIndex(where: { $0 > 25 })
print(firstHighestNumberIndex)
// Prints 1let 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 hashValue
property by themselves. In swift 4.1, this was improved so that hashValue
property 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: Intfunc 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 customhash(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 truelet 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:
- Calling off ImplicitlyUnwrappedOptional (IUO) type.
- Conditional conformances improvements.
- Iterating Enum cases.
- Dynamic member look up using dot syntax for subscripts.
- Warning and error diagnostic directives.
- In-built remove functionality based on certain conditions.
- Boolean toggling.
- Random number generation and shuffling.
- Method refactoring and adding new methods to Array.
- Hashable enhancements.
- Ability to check whether all elements in a Sequence satisfied a certain condition.
- 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.