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 the
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 an 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 to theHashable
protocol. -
SE-0207: New
allSatisfy()
method to verify all elements in a sequence pass a condition. - SE-0143: Conditional conformance improvements.
Enum Case Iteration
First up, Swift 4.2 introduces a new protocol for enums. If you make your enums conform to the CaseIterable
protocol, you will be able to 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 iterable, 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
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 feature is a blast from the past for many who came from an Objective-C background, as 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 to look at your code and complete the block of code, e.g. by 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 welcome 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, until Swift 4.1, the language lacked a native random number generator, forcing developers instead to 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, which 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, simpler, and more secure—through a new Hasher
struct. Previously, whenever you created dictionaries or sets, you would have a type that conformed to Hashable
, which gives you an optimized hash for free. However, when implementing your own type that conformed to Hashable
, you needed to create your own algorithm to calculate the hashValue
by hand.
Swift 4.1 improved this significantly by inferring what could be used to uniquely identify the object:
class Shirt: Hashable { var size: String var color: ColorEnum }
However, when you had to work with a more complex object type, you still needed to implement an algorithm to return a unique hashValue
.
Swift 4.2 introduces the new Hasher
struct, which can calculate a 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 satisfied a condition, the allSatisfy
method returns a boolean based on whether the entire set of elements satisfies 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.
Powered by WPeMatico