Swift Tips

#29 Foundation’s partition(by:) is useful if we want to divide a collection into two parts and operate on both sub-collections. Example could be partitioning index paths and calling UITableView reloadRows for one part and reconfigureRows for the other part.

var indexPaths = changingIndexPaths
let reconfiguredFirstIndex = indexPaths.partition(by: canReconfigure(_:))
let reloadedIndexPaths = indexPaths[0..<reconfiguredFirstIndex] // to UITableView reloadRows
let reconfiguredIndexPaths = indexPaths[reconfiguredFirstIndex...] // to UITableView reconfigureRows

#28 When dealing with shared instances, sometimes it makes sense to introduce static subscript for skipping typing .shared everywhere.

@dynamicMemberLookup
final class DependencyContainer {
    static let shared = DependencyContainer()

    let database: Database
    // …
}

extension DependencyContainer {
    static subscript<T>(dynamicMember keyPath: KeyPath<DependencyContainer, T>) -> T {
        return shared[keyPath: keyPath]
    }
}

let before = DependencyContainer.shared.database
let after = DependencyContainer.database

#27 Using enums over structs for namespacing static constants. Nicer, since we can’t create an instance of AppConstants nor AppConstants.URLs.

enum AppConstants {
    enum URLs {
        static let api = URL(string: "https://example.com/api/data")!
        // …
    }
    // …
}

#26 @autoclosure enables wrapping function parameters automatically with a closure, which means we can just pass a value, not a closure, to the function. Useful for custom logging functions where we might want to skip running the closure all together, depending on the build settings.

func debugLog( _ message: @autoclosure () -> String) {
    #if DEBUG
    print(message())
    #endif
}

debugLog("greeting")

#25 Destructuring a tuple is a short way to extract values from tuples in Swift

let timestamps = (start: Date.distantPast, end: Date.distantFuture)
// Tuple destructuring
let (start, end) = timestamps
print(start, end)
// vs more verbose
let start2 = timestamps.start
let end2 = timestamps.end
print(start2, end2)

#24 In addition to functions and properties, we can add init methods to protocols in Swift. In that case, the conforming non-final class needs to use the “required” modifier. This ensures that the init is explicitly implemented by the class or its subclasses.

#23 Swift 5.9 added sleep(for:tolerance:) to the Clock protocol, which makes it simpler to add delays. Before, we needed to use an instant.

do {
try await ContinuousClock().sleep(for: .seconds(5))
showsLabel = true
}
catch {
// …
}

#22 If a function in Swift has multiple defer statements, then these are run in a reverse order. Something to keep in mind if the order happens to be important.

func start() {
defer { print("1") }
defer { print("2") }
print(#function)
}
// Returns:
// start()
// 2
// 1

#21 We can opt in to upcoming Swift features with -enable-upcoming-feature Swift build flag. For example, in Swift 5.8 we can opt in to a new #file behaviour where it equals to the module_name/file_name instead of the full path. Useful when implementing custom logging functions.

#20 Swift 5.8 implements @backDeployed attribute for adding functionality to older API versions.

// This API is available in iOS 16, macOS 13.0, tvOS 16.0, watchOS 9.0.
// Using backDeployed we can make it available in old OSes, like iOS 15 etc
extension Font {
@backDeployed(before: iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0)
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
public static func system(size: CGFloat, weight: Font.Weight? = nil, design: Font.Design? = nil) -> Font {
fatalError("Implement")
}
}

#19 When using switch for enums with associated values, and we do not want to use associated values, then we can only write out the case name .error (instead of .error(_)).

enum State {
case loading
case error(Error)
case view
}
func logState() {
switch state {
case .loading:
print("Showing loading view")
case .error: // or case .error(_):
print("Showing error view")
case .view:
print("Showing content view")
}
}

#18 Adding custom initializer in an extension does not override the auto-generated initializer for structs in Swift.

struct Circle {
let centre: CGPoint
let radius: Double
}
extension Circle {
init(diameter: Double) {
centre = . zero
radius = diameter / 2
}
}
// Auto-generated is kept because init is in an extension
let circle = Circle(centre: .zero, radius: 3)
// Custom
let circle2 = Circle(diameter: 6)

#17 Additional concurrency checks can be enabled for async-await Swift code by adding -Xfrontend -warn-concurrency -enable-actor-data-race-checks to Other Swift Flags in Xcode

#16 Swift 5.7 introduced a new ContinuousClock API which also provides a nice way to measure time of async functions.

let clock = ContinuousClock ()
let elapsed = await clock.measure {
await compute()
}
print(elapsed) // 1.062192235 seconds

#15 Async property getters (but not setters) in Swift are written by adding the async keyword next to the get.

var avatar: UIImage? {
get async {
await imageLoader.loadImage(for: "myidentifier")
}
}

#14 Actor is a reference type which only allows one operation on their state at a time. Meaning, we can write code modifying multiple properties without worrying that these properties are read while we are still updating them.

actor DiapasonTracker {
private var minValue: Int = 0
private var maxValue: Int = 0
func receive(_ batch: [Int]) {
guard let batchMin = batch.min() else { return }
minValue = min(minValue, batchMin)
guard let batchMax = batch.max() else { return }
maxValue = max(maxValue, batchMax)
}
// Here diapason can't be accessed
// at the same time when receive() is
// the middle of updating
// minValue and/or maxValue.
// Using class instead of actor would
// allow one thread to update values
// while another is accessing diapason
// property which leads to inconsistencies.
var diapason: Int {
maxValue – minValue
}
}

#13 Optional pattern matching in for loop only loops over non-nil values.

let delegates: [SomeDelegate?] = // …
for case let delegate? in delegates {
print(delegate) // prints only non-nil delegates
}

#12 Swift 5.7 introduced if let shorthands. No more if let variable = variable and instead just if let variable.

#11 I have found the Foundation provided merge function for dictionaries a bit confusing to read. This is a bit shorter version I have preferred to use instead:

let first = ["a": 1, "b": 2]
let second = ["a": 9, "c": 3]
// value in `first` wins if the same key in both
let merged1 = first.merging(second, uniquingKeysWith: { current, _ in current })
// value in `second` wins if the same key in both
let merged2 = first.merging(second, uniquingKeysWith: { _, new in new })
extension Dictionary {
func mergingUniqueKeys(from other: [Key: Value]) -> [Key: Value] {
merging(other, uniquingKeysWith: { current, _ in current })
}
}
// value in `first` wins if the same key in both
let merged3 = first.mergingUniqueKeys(from: second)
// -> ["b": 2, "a": 1, "c": 3]
// value in `second` wins if the same key in both
let merged4 = second.mergingUniqueKeys(from: first)
// -> ["b": 2, "c": 3, "a": 9]

#10 #unavailable() allows switching API usage based on the OS version.

if #unavailable(iOS 15.0) {
// Calling old API
} else {
// Calling new API introduced in 15.0
}

#9 Swift.org has API design guidelines page. My go-to place when I am stuck with naming something.

#8 Dictionary has a subscript with default value. Useful if accessing the dictionary should return a fallback value when the dictionary key is not present.

let errorMessages = [
-1: "Failed to read the file"
//…
]
let errorCode = -2
let message = errorMessages[errorCode, default: "Unknown error occured" ]
print(message)
// Unknown error occured
view raw SwiftTip8.swift hosted with ❤ by GitHub

#7 Comparing boolean conditions with switch instead of if-elseif-elseif-else. Additionally, the compiler makes sure that all the possible cases are handled.

let isPlayerlWinning: Bool = // …
let isPlayer2Winning: Bool = // …
switch (isPlayerlWinning, isPlayer2Winning) {
case (true, true):
handleTie()
case (true, false):
handlePlayerlWinning()
case (false, true):
handlePlayer2Winning()
case (false, false):
handleContinue()
}
view raw SwiftTip7.swift hosted with ❤ by GitHub

#6 Add a function with default argument values in protocol extension. In protocol extension, 'self' points at the type implementing the protocol function.

protocol FormSubmitting {
func submitForm(_ form: Form, validates: Bool, timeout: TimeInterval)
}
extension FormSubmitting {
func submitForm(_ form: Form,
validates: Bool = true,
timeout: TimeInterval = 60) {
self.submitForm(form, validates: validates, timeout: timeout)
}
}
class MyObject: FormSubmitting {
func submitForm(_ form: Form, validates: Bool, timeout: TimeInterval) {
print(#function, validates, timeout)
}
}
let object = MyObject()
object.submitForm(form)
// submitForm_ :validates: timeout:) true 60.0
object.submitForm(form, validates: false, timeout: 30)
// submitForm_: validates: timeout:) false 30.0
view raw SwiftTip6.swift hosted with ❤ by GitHub

#5 HTTPURLResponse has a localizedString(forStatusCode:) function for returning localized strings for HTTP status codes.

HTTPURLResponse.localizedString(forStatusCode: 404)
// not found
HTTPURLResponse. HTTPURLResponse.localizedString(forStatusCode: 414)
// requested URL too long
view raw SwiftTip5.swift hosted with ❤ by GitHub

#4 Use dump() for printing out all the properties of a type. Especially useful when the type has a custom description which reveals only some information.

struct Contact {
let firstName: String
let lastName: String
let idCode: String
let address: CNPostalAddress
}
extension Contact: CustomStringConvertible {
var description: String {
"\(firstName) \(lastName)"
}
}
print (contact) // prints description
// John Doe
dump(contact) // prints all the properties v John Doe
// – firstName: "John"
// – lastName: "Doe"
// – idCode:
// "id123"
// – address: <CMutablePostalAddress: 0x600000338410: street=, subLocality=, city=Tokyo, subAdministrativeArea=, state=, postalCode=, country=Japan, countryCode=> #0
// – super: CNPostalAddress
// – super: NSObject
view raw SwiftTip4.swift hosted with ❤ by GitHub

#3 Avoiding default in switch statements which will trigger a compiler error after adding a new case and forces reviewing all the related code.

enum State {
case idle, pending, busy
let someState: State = .pending
switch someState {
case .idle:
loadNext ( )
case .pending:
showPendingLabel()
case .busy:
break
view raw SwiftTip3.swift hosted with ❤ by GitHub

#2 Converting integer to Data and back. Useful when working with raw bytes.

var input = UInt16(1024)
// UInt16 -› Data
let data = Data(bytes: &input, count: MemoryLayout<UInt16>.size)
// Data -> UInt16
let output = data.withUnsafeBytes({ $0.load(as: UInt16.self) })
print(output) // 1024
view raw SwiftTip2.swift hosted with ❤ by GitHub

#1 Avoid optional when converting data to utf8 string with init(decoding:as:).

let data = Data("abc".utf8)
let string = String(decoding: data, as: UTF8.self)
print(string)
//abc
view raw SwiftTip1.swift hosted with ❤ by GitHub