let hello =

One minute Swift reads to get Swifty-er

Updated for Swift 5.8

Shorthand for unwrapping optional values

May 26, 2023

As of Swift 5.7, there is a new shorthand for unwrapping optional values for later use.

func greet(userName: String?) { // Previous way -> if let userName = userName if let userName { print("Hello \(userName)!") } else { print("Hello!") } } func addMoney(amount: Double?, to otherAmount: Double) -> Double { // Previous way -> guard let amount = amount else {... guard let amount else { return otherAmount } return amount + otherAmount }

Getting random values

May 20, 2023

Swift ships with several ways to get random values ranging from picking a number within a range to random within an array that you've defined.

let numberLine = Double.random(in: 1 ... 100) let flipCoin = Bool.random() let rollDie = Int.random(in: 1 ... 6) let drinks = ["water", "cola", "apple juice", "orange juice"] let drinkRoulette = drinks.randomElement()

Providing a default value when accessing a dictionary

May 12, 2023

The typical way to get values out of a dictionary is via subscript using the dictionary's "key." This access method returns an optional value in return since the key may not exist. If you want a "fallback" value when the key doesn't exist, you can provide default value for that situation in the subscript. Also in this case, you don't have to deal with the returned value being wrapped in an optional!

enum FoodGroup { case fruits, grains, protein, vegetables } // no food to eat :( let availableFood: [FoodGroup: [String]] = [:] // Using a default value - we don't have to deal with the value being optional func quantityToEat(for group: FoodGroup) -> Int { availableFood[group, default: []].count } // Without a default value - we need to deal with the optional func quantityToEat2(for group: FoodGroup) -> Int { (availableFood[group] ?? []).count }

Generating JSON: Convert camel case to snake case

April 26, 2023

A lot of backend APIs expect JSON to use snake case (e.g., "user_id"), but the Swift convention is to use camel case (e.g. "userId"). Turns out there is a simple way to convert between the two when generating JSON.

import Foundation struct Transaction: Encodable { let id: String let userId: String let startDate: Date let endDate: Date } func uploadLatestsTransaction() { // get the latest transaction let transaction = Transaction(id: "123", userId: "456", startDate: Date(), endDate: Date()) let jsonEncoder = JSONEncoder() // >>> This line tells the encoder to convert between the two styles <<< jsonEncoder.keyEncodingStrategy = .convertToSnakeCase guard let jsonData = try? jsonEncoder.encode(transaction) else { return } print(String(decoding: jsonData, as: UTF8.self)) /* e.g., { "id":"123", "end_date":704374697.15610504, "user_id":"456", "start_date":704374697.15610504 } */ // upload `jsonData` }

Avoid declaring types if they can be inferred

April 21, 2023

Swift's type system is powerful. As long as the type on the right hand side can be inferred, you can leave off the type from the left when declaring a constant or variable.

// Leveraging Swift's type inference (Swifty-er!) let seven = 7 // type is an int let sevenPointZero = 7.0 // type is a double let luckyNumbers = [7, 7, 7] // type is an Array<Int> // Mostly bias away from doing this let five: Int = 5 let fivePointZero: Double = 5.0 let maybeLuckyNumbers: [Int] = [5, 5, 5]

Intro to dependency injection (it's not that scary)

April 15, 2023

This can be a "scary" subject/phrase, but it really boils down to something quite simple and nice: Give your entities (e.g., classes, structs, functions) what they "need" to do their job. You "give" your entities what they "need" via functions like initializers. If you do, reasoning about your code, writing tests, and future support will be much easier.

import Foundation struct Event { let Date: Date } func time(fromNow now: () -> Date, to event: Event) -> TimeInterval { // Injecting a way to get the current date will be useful for testing, it makes // the dependency more obvious for this function, and helps the readability. // By "injecting" the current time, we can "control" the current time, the resulting // returned time interval, and create a reliable test for the internals here. // Calculate the time interval .zero }

Prefer returning enums (even simple ones) over nil

April 8, 2023

Optionals are great, however information can be lost when used to represent the absence of something. An enum with as few as two cases can be very useful in conveying why something is missing, and is more accomidating in the future as things change.

struct Profile {} struct Stats {} // Weak - Using a returned optional hides failure or error information func stats(for profile: Profile) -> Stats? { // Get stats (e.g., database, network, cache) // If the above fails, the returned value of nil hides why nil } // Preferred - Using an enum helps convey more information enum RetrievedStats { case stats(Stats) case noneFound // More cases can be easily added over time as new ways to get stats are added! case databaseConnectionIssue case networkFailure // maybe with status code } func stats(for profile: Profile) -> RetrievedStats { // Get stats (e.g., database, network, cache) .noneFound }

Use property observers to "tap" into a property's lifecycle

March 31, 2023

The "willSet" and "didSet" property observers will get called every time a property is changed. This allows you react to changes in a property's value. E.g., saving that value to disk each time it is changed. They are not called on init however.

struct ViewData { let title: String let onAccept: () -> Void var userInput: String? { // Called before _userInput_ is set (the new value is provided) // Note: "newValue" can be renamed like this -> willSet(<name>) willSet { print("willSet: \(newValue ?? "nil")") } // Called after a _userInput_ is set (the old value is provided) // Note: "oldValue" can be renamed like this -> didSet(<name>) didSet { print("didSet: \(oldValue ?? "nil")") } } }

Ways to get the first element from an Array

March 25, 2023

Swift offers different ways to get the first element from an array depending on what you want to accomplish. For example, we can remove the first item from an array while returning it for use.

struct Follower { // ... } func firstFollower() -> Follower { // get followers from somewhere like a backend API call var followers: [Follower] = [.init(), .init(), .init()] // == Way 1 == // Using a subscript is a common way languages support getting the first item. // Note: This can crash if the array is empty. let firstFollower = followers[0] // == Way 2 == // Use this to mutate the array itself and return the removed item for use. // Note: This can crash if the array is empty. let _ = followers.removeFirst() // == Way 3 == // This is the safest way to get the first element since the returned item // is optional. // Note: This won't mutate the original array. Also, it will return nil if the // array is empty. let _ = followers.first return firstFollower }

The Optional type is actually an enum + syntactic sugar

March 17, 2023

Optionals are a powerful language construct that force us to address if something exists or not, but did you know that it's just an enum with two cases behind the scenes?

func printName() { let name: String? = Optional.some("Bob") switch name { case .none: print("Name doesn't exist") case let .some(name): print("Name is: \(name)") } }

Avoid using switch statement "default" cases with enums

March 10, 2023

One way to get the most out of enums in Swift is taking advantage of exhaustive switch statement checking. The compiler can check to ensure all cases are handled in a switch statement. This means if you add another case to an enum, the compiler will "guide" you through addressing that case addition by raising errors where that new case isn't handled. Using a "default" will silently handle that new enum case, which may not be what you want.

enum Shapes { case square case circle case triangle // Using a default can lead to future bugs. // For example, adding a "pentagon" case would by default // return false below. var hasMoreThan3Sides: Bool { switch self { case .square: return true default: return false } } // This version is much better. The compiler will "ask" // you, via an error, to address how many sides that new // shape has below (aka Switch must be exhaustive). var betterHasMoreThan3Sides: Bool { switch self { case .square: return true case .circle, .triangle: return false } } }

Implicit "error" in catch blocks

March 3, 2023

When you have a do/try/catch, there is an implicit error available in the "catch block" that can be used to better understand what went wrong. That local implicit error can also be explicitly named if desired.

import Foundation // Implicit error in the catch block func retrievePost(url: URL) { do { let post = try String(contentsOf: url) print(post) // do something with the post } catch { // notice no error was defined print(error.localizedDescription) } } // The error can be explicitly named func retrievePost2(url: URL) { do { let post = try String(contentsOf: url) print(post) // do something with the post } catch let postError { print(postError.localizedDescription) } }

Getting an enum's associated value using an if statement

February 24, 2023

As an alternative to a full switch statement to match an enum and get an enum's associated value out, a regular if statement can be used for a single case at a time. This is handy to know, but the "if statement" syntax to check an enum and get its associated value is tricky and hard to remember. Is it "if case let variable = enum" or "if let case enum = variable" or...?

enum Animal { case dog(breed: String) case cat } // If you want an enum's associated value func breedIfDog(_ maybeDog: Animal) -> String? { if case let .dog(breed) = maybeDog { return breed } return nil } // Or, if you don't want the associated value func isDog(_ maybeDog: Animal) -> Bool { // a `guard` statement might be better here (e.g., "guard case .dog = maybeDog else { return false }") if case .dog = maybeDog { return true } else { return false } }

Terser code by using optional's "map"

February 17, 2023

If you need to transform an optional when the value exists, making use of the higher order "map" function on optionals will make your code shorter and clearer. The map function on optional will apply a function to transform a value if it exists, then pack it back up in an optional.

// Without using "map" func upperCase(userInput input: String?) -> String? { if let input { return input.uppercased() } return nil } // Using "map" func upperCase2(userInput input: String?) -> String? { // this can probably just be inlined where needed now input.map { $0.uppercased() } }

Prefer "guard" statements for early returns

February 10, 2023

The guard statement allows execution to continue if the condition provided is met, or early returns if that condition is not met. Using a guard, we can easily know by inspection that a statement may return early, and therefore better understand the control flow. This is because the compiler "checks" and enforces an early return for guard statements.

enum PasswordValidation: Error { case invalidAndUnknown } func isPasswordValid(_ password: String?) throws -> Bool { // Throwing is a possible way to guarantee an early exit (if the function is throwing) // If password isn't nil, the guard passes and unwraps "password" to be used as non-optional guard let password else { throw PasswordValidation.invalidAndUnknown } // These guards provide boolean early returns (per the return type of this function) guard password.isEmpty else { return false } guard password.count > 8 else { return false } // The compiler can still guarantee that this guard will exit early - because this will crash... guard password == "1234" else { fatalError() } // Below will not compile because there is no early exit in the "else" clause // Error: 'guard' body must not fall through, consider using a 'return' or 'throw' to exit the scope // guard password.contains(where: { char in char == "!"}) else { } return true }

Looping through an array with an index

February 3, 2023

Using `.enumerated()` on an array will return a pair of index & element for use in each iteration of the loop.

Note: Not all collections use zero-based and integer indicies. For example, if you have a set, then `.enumerated()` won't function the same. There isn't an order to sets. In this case, the "index" mentioned above is more of a "counter" for an undefined order.

let favoriteColors = ["Blue", "Yellow", "Red", "Orange"] func printFavoriteColors() { // Using enumerated: for loop for (index, favorite) in favoriteColors.enumerated() { let place = index + 1 // add 1 since enumerated starts at 0 print("\(place): " + favorite) // 1: Blue, 2: Yellow, 3: Red, 4: Orange } // Using enumerated: `forEach` higher-order function favoriteColors.enumerated().forEach { index, favorite in let place = index + 1 print("\(place): " + favorite) // same result as the for loop } }

Using key paths for a cleaner style (point-free notation)

January 27, 2023

We can make code a little cleaner by using point-free style. This means that we avoid referring to the value in a function (aka a "point"). Key paths let us use this style since they allow us to access a nested property.

import Foundation // example entity struct User { let id: UUID let name: String let email: String } extension User { // convenience static var randomId: User { User( id: UUID(), name: "Rob", email: "[email protected]" ) } } let users: [User] = [.randomId, .randomId, .randomId, .randomId] // This is not point-free since the value `$0` is used let userIds = users.map { $0.id } // No mention of the value on which the function is acting let userIds2 = users.map(\.id)

Defer assigning a "let" and avoid using "var"s

January 20, 2023

Constants (i.e., "let"s) can have a deferred assignment (aka definite initialization) as long as the compiler can guarantee that it is assigned in every code path and only once. Useful to avoid using a "var", and a safer pattern to use since a constant can't be changed later.

import Foundation enum Route { case url(URL) case fallback } func showStats(using route: Route) { // Deferred constant assignment let url: URL // The compiler can guarantee that this is assigned below switch route { case let .url(routeUrl): url = routeUrl case .fallback: url = URL(string: "api.example.com")! // (force unwraps should be avoided) } // Make use of the url print(url) }

Defining Constants and Variables

January 13, 2023

Constants and Variables need to start with the "let" and "var" keywords respectively. Constants cannot be changed after being set (immutable) while variables can be changed (mutable).

// Constant let name1 = "Swifty" // constantName = "SwiftyNotes" // <- The Swift compiler won't allow this // Variable var name2 = "Swifty" func changeName() { name2 = "SwiftyNotes" // <- Change the variable }