Representing Errors

Using an enum that conforms to the error protocol

import Foundation

struct ParsingError: Error {
	enum MyError: Error {
		case minor
		case bad
		case terrible(description: String)
	}

	let line: Int
	let column: Int
	let kind: MyError
}


func parse(_ source: String) throws {
	throw ParsingError(line: 1, column: 1, kind: .bad)
}

Probably don’t want to use standard enums with asynchronous code.

enum Result<Value, Error> {
	case success(Value)
	case failure(Error)
}

func fetch(_ request: URLRequest, completion: (Result<(URLResponse, Data), Never>) -> Void) {

}


let request = URLRequest(url: URL(string: "a")!)
fetch(request) {
	request in

	switch result {
		case let .success(response, _): print("Success: \(response)")
	}
}

Handling Errors

import Foundation

class Fridge {
	enum FridgeError: Error {
		case contentNotPresent
		case contentExpired(name: String)
	}

	private var content = [
		"UID01" = "Juice"
	]

	private var expiredContent = [
		"UID02": "Apple"
	]

	func getContentNameWith(contentID: String) throws -> String {
		if let name = expiredContent[contentID] {
			throw FridgeError.contentExpired(name: name)
		}

		guard let name = content[content] else {
			throw FridgeError.contentNotPresent
		}

		return name
	}

	func getContentWithOperation(operation: () throws -> String) rethrows -> String {
		return try operation()
	}
}


print(contentName)

// To mitigate try

// 1. Do catch
do {
	let fridge = Fridge()
	let  contentName = try fridge.getContentNameWith(contentID: "UdD01")
} catch {
	print("failed")
}

// Optional try?
let contentNameOp = try? fridge.getContentNameWith(contentID: "U101")

// Forceful try! Not recommended
let contentNameOp = try? fridge.getContentNameWith(contentID: "U101")

// Extend String to be an error
extension String: Error {}
throw "Some error"

Unit Testing

A unit test should be automated and repeatable It should be easy to implement once it’s written, it should remain for fututre use Anyone should be able to run it Should be run by the push of a button

import XCTest

class ScoreKeeper {
	private var score = 0

	func getScore() -> Int {
		return score
	}

	func incrementScore() {
		score += 1
	}

	func incrementScore(_ completion: @escaping (_ newScore: Int) -> Void) {
		incrementScore()
		DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
			completion(self.score)
		})
	}
}

class ScoreKeeperTests: XCTestCase {
	var keeper: ScoreKeeper!

	override func setup() {
		super.setUp()
		keeper = ScoreKeeper()
	}

	func testInitialGetScore() {
		XCTAssert(keeper.getScore() = 0)
	}

	func testIncrementScore() {
		keeper.incrementScore()
		XCTAssert(keeper.getScore() == 1)
	}

	func testScoreIncrementPerformance() {
		measure {
			keeper.incrementScore()
		}
	}

	func testAsyncIncrementScore() {
		let scoreExpectation = expectation(description: "Score returned")

		keeper.incrementScore {
			score in

			scoreExpectation.fulfill()
		}

		waitForExpectations(timeout: 0.1) { error in

		}
	}
}

ScoreKeeperTests.defaultTestSuite.run()

Writing Testable Code

Keeping it extensible with a DB.

Using dependency injection

import XCTest

protocol DataBase {
	func getWith(key: String) -> Int
	func incrementWith(key: String)
}

class RealDatabase: Database {
	func getWith(key: String) -> Int {
		return 1
	}

	func incrementWith(key: String) {
	}
}

// Injection through initializer

class ScoreKeeper {
	private var database: Database

	init(database: Database = RealDatabase()) {
		self.database = database
	}

	func getScore() -> Int {
		return database.getWith(key: scoreKey)
	}

	func incrementScore() {
		database.incrementWith(key: scoreKey)
	}
}

class MockDatabase: Database {
	// Mock everything
	var value = 1

	func getWith(key: String) -> Int {
		return value
	}

	func incrementWith(key: String) {
		value += 1
	}
}

class ScoreKeeperTests: XCTestCase {
	var keeper: ScoreKeeper!

	override func setup() {
		super.setUp()
		let database = MockDatabase()
		database.value = 0

		keeper = ScoreKeeper(database: database)
	}

	func testInitialGetScore() {
		XCTAssert(keeper.getScore() = 0)
	}

	func testIncrementScore() {
		keeper.incrementScore()
		XCTAssert(keeper.getScore() == 1)
	}

	func testScoreIncrementPerformance() {
		measure {
			keeper.incrementScore()
		}
	}

	func testAsyncIncrementScore() {
		let scoreExpectation = expectation(description: "Score returned")

		keeper.incrementScore {
			score in

			scoreExpectation.fulfill()
		}

		waitForExpectations(timeout: 0.1) { error in

		}
	}

	func testAsyncIncrementScore() {
		let scoreExpectation = expectation(description: "Score returned")

		keeper.incrementScore {
			score in

			scoreExpectation.fulfill()
		}

		waitForExpectations(timeout: 0.1) { error in

		}
	}
}

ScoreKeeperTests.defaultTestSuite.run()

Enhance your code with Generics

Type Constraints

func exists<T: Equatable>(item: T, in array: [T]) -> Bool {
	for arrayItem in array {
		it item == arrayItem {
			return true
		}
	}
	return false
}

let stringArray = ["a", "b"]
let intArray = [1, 2]
exists(item: "t", in: stringArray)
exists(item: 3, in: intArray)

Associated type gives a placeholder name to a type that is used as a part of the protocol

The actual type to use for that associated type isn’t specified until the protocol is adopted.

Associated types are specified with the associatedtype keyword

protocol GenericProtocol {

	associatedtype MyType

	var property: MyType { get set }
}

class StringImplementation: GenericProtocol {
	typealias MyType = String

	var property: MyType = "a"
}

class IntImplementation: GenericProtocol {
	typealias MyType = Int

	var property: MyType = 1
}

var string = StringImplementation()
print(string.property)

var int = IntImplementation()
print(int.property)

The Where Clause

Where clause are used to define requirements in associated types

protocol Money {
	associatedtype Currency
	var currency: Currency { get }
	var amount: Float { get }

	func sum<M: Money>(with money: M) -> M? where M.Currency = Currency
}

class Euro {}
class Pound {}

class IrishMoney: Money {
	typealias Currency = Euro

	var currency = Euro()
	var ammount: Float = 10.0

	func sum<M>(with money: M) -> M? where M: Money, Irish.Currency == M.Currency {
		return nil
	}
}

Can only add the same currency with the protocol

protocol Company {
	associatedtype TradeCurrency
	func buy<T: Tradable, M: Money>(product: T, with money: M) -> T? where M.Currency == TradeCurrency
}

class BershireHathaway: Company {
	typealias tradeCurrency = Euro

	func buy<T, M>(product: T, with money: M) -> T? where T: Tradable, M: Money, BerkshireHathaway.TradeCurrency == M.Currency {
		return nil
	}
}

class Oil: Tradabel {}
var berk =  BerkshireHathaway()
let oil = berk.buy(product: Oil(), with: Irish())

A Generic Networking Example

import Foundation

protocol Request {
	associatedtype Response
	associatedtype Error

	var baseURL: URL { get }
	var method: String { get }
}