Let’s play with swift-part 5

Pattern, Error handling

If you want to learn more please read chapter-20 and chapter-21 of Swift Apprentice.

Pattern

Wildcard pattern

The pattern in this case condition uses an underscore, _, to match any value of x component and exactly 0 for the y and z components. Here (“_”) is the wildcard pattern and (0, 0) is the expression pattern. Where expression pattern use ~= operator to compare the expression with the value.

Value-binding pattern

From the above example we are ok with any value of x but we can not access the value. In value binding way we can give a variable instead of “_ “ and that variable hold the value which we can use letter.

If you wanted to bind multiple values, you could write let multiple times or, even better, move the let outside the tuple:

By putting the let on the outside of the tuple, the compiler will bind all the unknown constant names it finds.

Enumeration case pattern

let pet = Organism.animal(legs: 4)
if case .animal = pet {
print("it's an animal.")
}

it’s a simple enumeration case pattern. We can use value binding pattern with it to get associated value.

“Is” type-casting pattern

for element in response {
switch element {
case is String: print("Found a string") // 1 time
default: print("Found something else")
}
}

With this code, you find out that one of the elements is of type String. But you don’t have access to the value of that String in the implementation.

“As” type-casting pattern

The as operator combines the is type casting pattern with the value-binding pattern. Extending the example above, you could write a case like this:

So when the compiler finds an object that it can cast to a String, the compiler will bind the value to the text constant.

Qualifying with where

let levels: [LevelStatus] =[.complete, .inProgress(percent: 0.9), .notStarted]for level in levels {
switch level {
case .inProgress(let percent) where percent > 0.8 :
print("Almost there!")
case .inProgress(let percent) where percent > 0.5 :
print("Halfway there!")
case .inProgress(let percent) where percent > 0.2 :
print("Made it through the beginning!")
default:
break
}
}

So here we use enumeration case pattern then value binding and then we use where to use this value to take decision.

Chaining with commas

let timeOfDay = timeOfDayDescription(hour: 12)

Here you see several identifier patterns matched in each case condition using comma chaining.

let pet = Organism.animal(legs: 4)if case .animal(let legs) = pet, case 2...4 = legs {
print("potentially cuddly") // Printed!
} else {
print("no chance for cuddles")
}

Tuple pattern

So, tuple is nothing but a list of comma separated pattern. Tuple (x, 0, 0) is a list of (identifier, expression, expression) patterns.

Error handling

Fail-able initializers

Sometimes we need to create initializer which may not create the object all the time. Like when we try to convert a string into integer that time it may convert or may not. We can not guarantee it.

This is an example of fail-able initializer. Let’s create one.

Optional chaining

The diagram is: Person have or have not pet. Pet may or may not like toys. So pet have toys or not and the toys can make sound. Classes are Person, Pet, Toy.

enum Sound {
case squeak
case bell
}
let kind: Kind
let color: String
var sound: Sound?
init(kind: Kind, color: String, sound: Sound? = nil) {
self.kind = kind
self.color = color
self.sound = sound
}
}
class Pet {
enum Kind {
case dog
case cat
case guineaPig
}
let name: String
let kind: Kind
let favoriteToy: Toy?
init(name: String, kind: Kind, favoriteToy: Toy? = nil) {
self.name = name
self.kind = kind
self.favoriteToy = favoriteToy
}
}
class Person {
let pet: Pet?
init(pet: Pet? = nil) {
self.pet = pet
}
}
let janie = Person(pet: Pet(name: "Delia", kind: .dog, favoriteToy: Toy(kind: .ball, color: "Purple", sound: .bell)))let tammy = Person(pet: Pet(name: "Evil Cat Overlord", kind: .cat, favoriteToy: Toy(kind: .mouse, color: "Orange")))let felipe = Person()

Now, suppose we want to access the sound of a toy. So we can access it

it’s a long optional chaining. In several state it can break it it get nil. We can handle it better way. I already talk about map and compact map in my previous series. We can use compact map here. It will just filter out those instant which have data.

// output are
Optional("Delia")
Optional("Evil Cat Overlord")
nil

Now use compact map

// output are
Delia
Evil Cat Overlord

Error Protocol

init(flavor: String, numberOnHand: Int) {
self.flavor = flavor
self.numberOnHand = numberOnHand
}
}
enum BakeryError: Error{
case tooFew(numberOnHand: Int)
case doNotSell
case wrongFlavor
}

The Error protocol tells the compiler that this enumeration can be used to represent errors that can be thrown.

Throwing errors

var itemsForSale = [
"Cookie": Pastry(flavor: "ChocolateChip", numberOnHand: 20),
"PopTart": Pastry(flavor: "WildBerry", numberOnHand: 13),
"Donut" : Pastry(flavor: "Sprinkles", numberOnHand: 24),
"HandPie": Pastry(flavor: "Cherry", numberOnHand: 6)
]
func orderPastry(item: String, quantity: Int, flavor: String) throws -> Int {guard let item = itemsForSale[item] else {
throw BakeryError.doNotSell
}
guard flavor == item.flavor else {
throw BakeryError.wrongFlavor
}
guard quantity <= item.numberOnHand else {
throw BakeryError.tooFew(numberOnHand: item.numberOnHand)
}
item.numberOnHand -= quantity
return quantity
}
}

Handling errors

  try bakery.orderPastry(item: "Albatross",amountRequested: 1,flavor: "AlbatrossFlavor")} catch BakeryError.doNotSell {
print("Sorry, but we don’t sell this item.")
} catch BakeryError.wrongFlavor {
print("Sorry, but we don’t carry this flavor.")
} catch BakeryError.tooFew {
print("Sorry, we don’t have enough items to fulfill your
order.")
}

Code that can throw errors must always be inside a do block which creates a new scope. Even more, the exact points where errors can be thrown must be marked with try. The try above doesn’t actually do anything. It serves as a reminder so that whoever reads your code can easily understand what can go wrong.

Not looking at the detailed error

If you don’t really care about the details of the error you can use try? to wrap the result of a function (or method) in an optional. The function will then return nil instead of throwing an error. No need to setup a do {} catch {} block.

Stoping your program on an error

Use it only when you are sure that your program do not go throw an error.

You can write more clean code by forcefully unwrapping the try!

Advance error handling

enum PugBotError: Error {
case invalidMove(found: Direction, expected: Direction)
case endOfPath
}
class PugBot {
let name: String
let correctPath: [Direction]
private var currentStepInPath = 0
init(name: String, correctPath: [Direction]) {
self.correctPath = correctPath
self.name = name
}
func move(_ direction: Direction) throws {
guard currentStepInPath < correctPath.count else {
throw PugBotError.endOfPath
}
let nextDirection = correctPath[currentStepInPath]
guard nextDirection == direction else {
throw PugBotError.invalidMove(found: direction, expected: nextDirection)
}
currentStepInPath += 1
}
func reset() {
currentStepInPath = 0
}
}

Let’s create pug and a function goHome.

func goHome() throws {
try pug.move(.forward)
try pug.move(.left)
try pug.move(.left)
try pug.move(.right)
}

Now this goHome function also throw the error. It do not handle itself. So let call the function.

It will work perfectly but we can handle it better way. We can pass this function into another function parameter the call that parameter the handle our error inside that function. Let’s do it.

let’s call it.

// output = The PugBot was supposed to move forward,but moved left instead.

We can also pass closure as a parameter

Rethrows

A function that takes a throwing closure as a parameter has to make a choice: either catch every error or be a throwing function. Let’s say you want a utility function to perform a certain movement, or set of movements, several times in a row. You could define this function as follows:

Error handling for asynchronous code

The do-try-catch mechanism works only for synchronous code. You can’t use throws to throw errors if you execute your code asynchronously.

Little bit of GCD

func addNumbers(upTo range: Int) -> Int {
log(message: "Adding numbers...")
return (1...range).reduce(0, +)
}
let queue = DispatchQueue(label: "queue")func execute<Result>(backgroundWork: @escaping () -> Result, mainWork: @escaping (Result)->()) {
queue.async {
let result = backgroundWork()
DispatchQueue.main.async {
mainWork(result)
}
}
}
execute(backgroundWork: {addNumbers(upTo: 100)} ) {log(message: "The sum is \($0)")}

The output is

So here in execute function we pass 2 parameter. The first one return Result object and it runs on background thread. So we can return error also from the first function then we can use those in our background function. Let’s do it.

let queue = DispatchQueue(label: "queue")struct Tutorial {
let title: String
let author: String
}
enum TutorialError: Error {
case rejected
}
func feedback(for tutorial: Tutorial) -> Result<String, TutorialError> {
Bool.random() ? .success("published") : .failure(.rejected)
}
func edit(tutorial: Tutorial){
queue.async {
let result = feedback(for: tutorial)
DispatchQueue.main.async {
switch result {
case .success(let data):
print("\(tutorial.title) by \(tutorial.author) was \(data) on the website.")
case let .failure(error):
print("\(tutorial.title) by \(tutorial.author) was \(error)")
}
}
}
}
let tutorial = Tutorial(title: "Do some update", author: "XYZ")
edit(tutorial: tutorial)

Those who follow or read please comment down if you have questions. Follow me if you love to read about testing and design pattern. I am planning to write on testing from iOS Unit Testing by Example. They write for UIKit. I will try to write it in SwiftUI. I also love to write more algorithm topic soon. So stay positive and we will learn many thing together. If you want to connect, knock me at LinkedIn. Happy coding and stay safe.

I work as a Sr. software engineer for iOS platform. Available to discuss about any good opportunities or projects.