
The second update release to Swift of 2018, Swift 4.2 introduces some great improvements to the language. Read this post to learn how they can help you write even better code.
Included in this version’s list of improvements:
- SE-0194: Adds
CaseIterable
protocol to automatically generate an array of all the possible enum cases. - SE-0195: Dynamic member lookup syntactical sugar.
- SE-0196: New compiler directives
#warning
and#error
. - SE-0197: New
removeAll(where:)
method to perform optimized in-place filter for collections. - SE-0199: New
toggle()
method to easily toggle/flip boolean values. - SE-0202: New native random number generator.
- SE-0206: New
Hasher
type to improve and simplify conformance toHashable
protocol. - SE-0207: New
allSatisfy()
method to verify all elements in a sequence pass a condition. - SE-0194: Conditional conformance improvements
Enum Case Iteration
First-up, Swift 4.2 introduces a new protocol for enums. If you conform your enums to the CaseIterable
protocol, you will be able iterate through your list of enums in an array-like fashion, listing out all the possible cases. For example, take the following enum:
enum ShirtSizeOptions: CaseIterable { case small, medium, large, extra-large }
You can iterate through this enum list like an iterateable, using the new allCases
property, as follows:
for size in ShirtSizeOptions.allCases{ print("Shirt size is \(size)") }
Dynamic Member Lookup
A new attribute, @dynamicMemberLookup
, has been added to allow the Swift compiler to utilize a subscript method when accessing properties so that you can provide dot syntax for arbitrary names, which are then resolved at runtime. This takes a leaf out of Python’s book of conventions. When defining a subscript, you pass in a dynamicMember
along with a list of properties that you will return, as follows:
@dynamicMemberLookup class Shirt { subscript(dynamicMember member: String) -> String { let values = ["size": "small", "color": "Light Blue", "type": "kids"] return values[member, default: ""] } }
This example illustrates receiving a dynamic member as a string and returning a string, whilst looking up the member name in the dictionary. The following implementation illustrates how this works:
let shirt = Shirt() print(shirt.size) //small print(shirt.color) //Light Blue print(shirt.gender) //
Instantiating the class, the first two properties are dynamically discoverable and will return the default value of the member in a type-safe String
back, at runtime. The last property (gender
) does not exist in the class and would return nothing back, since we used an empty string as the default when looking up return values. You are not restricted to returning Strings, you can in fact return anything from a dynamic member lookup, including closures.
New Compiler Warning and Error Directives
This new features is a blast from the past for many who came from an Objective-C background, the consortium introduces (or re-introduces) compiler directives for Swift to flag issues in your code. The two directives are #warning
and #error
.
The #warning
directive helps developers mark a block of code that has issues so these will show up as a warning in Xcode. The #error
directive however will force a compile-time error and is useful if for instance you want to force the developer looking to look at your code and to complete the block of code, for example adding his or her own API token or credentials in lieu of the placeholder. The following example illustrates the latter use case:
class APIServiceManager { #error("Please enter your own personal cloud token below") var cloudToken: String = "" }
New Method to Perform In-Place Filter for Collections
Through the new removeAll(where:)
method, developers can now perform in-place filtering on collections by passing a closure condition. Say you have an array of shirts, and want to remove a specific value, medium
, you would have previously done something like:
let shirtSizes = [“small”, “medium”, “large”, “extra-large”] let mediumShirtRemoved = shirtSizes.filter { !$0.contains(“medium”) } print(mediumShirtRemoved) // [“small“, “large“. “extra-large“]
With the addition of removeAll(where:)
, instead of filtering to remove, you can run a more memory-optimized operation that removes explicitly and in-place:
var shirtSizes = ["small", "medium", "large", "extra-large"] shirtSizes.removeAll { $0.contains("medium") } print(shirtSizes) //["small", "large", "extra-large"]
Easily Toggle or Flip Between Boolean Values
A simple but welcomed improvement, SE-0199 introduces boolean toggling through the toggle()
method. In a familiar pattern, you would have something like the following:
var isShirtLarge = true print(isShirtLarge) //true isShirtLarge = !isShirtLarge print(isShirtLarge) //false
With the addition of this new method, you could write instead:
var isShirtLarge = true print(isShirtLarge) //true isShirtLarge.toggle() print(isShirtLarge) //false
New Native Random Number Generator
Surprisingly the language up until Swift 4.1 lacked a native random generator, forcing developers to instead rely on arc4random_uniform()
to return a uniformly distributed random number. Now you can simply call the random()
method along with a specific range to work with:
var shirtSizes = [“small”, “medium”, “large”, “extra-large”] let randomShirt = Int.random(in: 0 ..< shirtSizes.count-1) print(shirtSizes[randomShirt]) //medium
This method is supported on other numerical types beyond Int
, including: Float
, Double
, CGFloat
, Bool
, and Array
.
The Bool
lets you get back a random true
or false
response. This proposal also called for two array-related methods, the shuffled()
method that lets you randomize an array order, and randomElement()
which returns a random element from an array.
Here's an example of how you would use the new shuffled()
method:
var shirtSizes = [“small”, “medium”, “large”, “extra-large”] let newShirtSizesOrder = shirtSizes.shuffled() print(newShirtSizesOrder) //[“large”, “medium”, “extra-large”, “small”]
We can also use the randomElement()
method to improve the code we had earlier to get a random shirt size:
var shirtSizes = ["small", "medium", "large", "extra-large"] //let randomShirt = Int.random(in: 0 ..< shirtSizes.count-1) //print(shirtSizes[randomShirt]) print(shirtSizes.randomElement()) //extra-large
Improvements to Hashable
Protocol Conformance
Swift has improved how your custom object types conform to the Hashable
protocol—making it faster, more secure and simpler—through a new Hasher
struct. Previously, whenever you created dictionaries or sets, you would have a type that conforms to Hashable
which gives you an optimized hash for free. However, when implementing your own type that conforms to Hashable
, you needed to create your own algorithm to calculate the hashValue
by hand.
Swift 4.1 improved this significantly by inferring what can be used to uniquely identify the object:
class Shirt: Hashable { var size: String var color: ColorEnum }
However when you have to work with a more complex object type you still needed to implement an algorithm to return a unique hashValue
back. Swift 4.2 introduces the new Hasher
struct that can calculate unique hash value for you:
struct Shirt: Hashable { static func == (lhs: Shirt, rhs: Shirt) -> Bool { return lhs.size == rhs.size } var size: Int? var id: Int? func hash(into hasher: inout Hasher) { hasher.combine(size) hasher.combine(id) } }
To implement the new Hasher
struct, you create a Hasher
instance and provide the custom types you want to combine. The Hasher instance will create and return a unique hash.
Ensure All Elements in a Sequence Satisfy a Condition
The final feature accepted into Swift 4.2 is SE-0207, which adds a new method, allSatisfy()
to verify whether all items in a sequence conform to a certain sequence condition. While you were already able to use the contains
method to verify whether an element in a collection satisfies a condition, the allSatisfy
method returns a boolean based on whether the entire set of elements satisfy a condition.
The following implementation of allSatisfy
asserts whether the entire sequence of shirtOrderPrices
is satisfied by the condition of being over $20:
var shirtOrderPrices = [42.99, 42.99, 23.90, 42.99, 32.99] let weHaveMedium = shirtOrderPrices.allSatisfy { $0 > 20 } //true
Conclusion
Swift remains a growing language with new features being added and debated all the time. You can track the most up-to-date list of proposals, and the acceptance status of each one, by visiting Swift Evolution.