Swift Generics & Advanced
Generics, opaque types, property wrappers, result builders, key paths
- 01
What are generics in Swift? How do where clauses work?
MediumGenerics let you write one implementation that works for many types while keeping full type safety — no
Any, no casting, no boxing.// Generic function with constraint func clamp<T: Comparable>(_ value: T, to range: ClosedRange<T>) -> T { min(max(value, range.lowerBound), range.upperBound) } // Generic type … - 02
What are opaque types (some) vs existentials (any) in Swift?
HardBoth
someandanylet you refer to a protocol, butsomenames one hidden concrete type whileanyis a box that can hold a different type each time.protocol Shape { func area() -> Double } struct Circle: Shape { let r: Double; func area() -> Double { .pi * r * r } } struct Square: Shape { let s: Double; func area() -> Double { s * s } } // some — single concrete type, hidden func randomShape() -> some Shape { Circle(r: 1) } … - 03
What are property wrappers in Swift?
HardProperty wrappers package storage + behavior into a reusable annotation.
// Define a wrapper @propertyWrapper struct Clamped<Value: Comparable> { private var value: Value let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { … - 04
What are result builders (DSLs) in Swift?
HardResult builders (originally function builders) let you write declarative DSLs in Swift.
// A toy HTML builder DSL @resultBuilder struct HTMLBuilder { static func buildBlock(_ parts: String...) -> String { parts.joined() } … - 05
What are key paths in Swift?
MediumA key path is a typed, storable reference to a property that you write with a backslash and pass around like any other value.
struct User { var name: String; var age: Int } let users = [User(name: "Alice", age: 30), User(name: "Bob", age: 25)] // Key path literal let kp = \User.name // KeyPath<User, String> print(users[0][keyPath: kp]) // Alice … - 06
Explain Codable in Swift and how to customize JSON encoding/decoding.
MediumCodable is just
Encodable & Decodable, and conforming makes the compiler synthesise a symmetric encoder and decoder over your stored properties.struct Article: Codable { let id: UUID let title: String let publishedAt: Date let tags: [String] let author: String … - 07
Your protocol has an associatedtype and you now need to store a mixed collection of conformers. What are the options?
HardA protocol with an associated type describes a family of types rather than one type, so before you can store conformers you have to either pin that associated type or erase it.
protocol Repository<Item> { // Item is a primary associated type associatedtype Item: Identifiable func fetch(id: Item.ID) async throws -> Item func save(_ item: Item) async throws } … - 08
What does @escaping actually change, and why is non-escaping the default?
MediumIt tells the compiler the closure may still be alive after the function returns, so the closure and everything it captured have to outlive the call.
final class ImageLoader { private var handlers: [(UIImage) -> Void] = [] // Runs before the function returns — nothing to annotate func withPlaceholder(_ body: (UIImage) -> Void) { body(.placeholder) … - 09
What are Swift macros, what kinds exist, and what do they cost?
MediumA macro is a compiler plugin that generates source code during the build, so what you ship is ordinary Swift you can read, step through and debug.
// ── Declaration (your library target) ────────────────────────── @attached(member, names: named(init), named(memberwiseValues)) @attached(extension, conformances: Codable) public macro Model() = #externalMacro(module: "AppMacros", type: "ModelMacro") @freestanding(expression) … - 10
Passing a big Array around in Swift looks free. How does copy-on-write work, and how would you implement it for your own type?
HardThe underlying buffer is shared between copies until somebody writes, and a uniqueness check decides whether that write has to clone first.
final class Storage { // private, final, single owner var pixels: [UInt8] init(pixels: [UInt8]) { self.pixels = pixels } func copy() -> Storage { Storage(pixels: pixels) } } … - 11
How do you make your own type work in a for-in loop, and what does adding lazy change?
MediumConforming to Sequence with an iterator is what the loop actually requires, and it hands you the whole standard library of algorithms at the same time.
struct Countdown: Sequence { let start: Int func makeIterator() -> Iterator { Iterator(current: start) } struct Iterator: IteratorProtocol { … - 12
How does SwiftUI let you write .buttonStyle(.bordered) instead of naming the style's type?
MediumA protocol extension constrained to one concrete conforming type can hold a static property, and leading-dot syntax finds it from the type the compiler expects at that position.
protocol Validator { associatedtype Value func validate(_ value: Value) throws } struct NotEmpty: Validator { … - 13
Xcode says it cannot type-check a fifteen-line expression in reasonable time and nothing in it looks wrong — what is the compiler doing?
MediumIt is solving one constraint system over every overload of every operator and every literal in that expression, and the search space multiplies with each term until a timer stops it.
import SwiftUI // ❌ One expression: the literals are still open (Int? Double? CGFloat?), + is // overloaded dozens of times, and each ternary makes the solver try both // branches against every candidate that is still standing. func slowPadding(isCompact: Bool, hasIcon: Bool, iconWidth: CGFloat, inset: CGFloat) -> CGFloat { … - 14
Your property wrapper's setter compiles in a class but not in a struct, yet SwiftUI writes to @State from an immutable view — how does that work?
MediumA
wrappedValuesetter that changes storage held inside the wrapper ismutating, and mutating the wrapper means mutating the value that contains it — unless the wrapper writes through a reference and declaresnonmutating set.// Storage lives inside the wrapper, so the setter must be mutating. @propertyWrapper struct Clamped<Value: Comparable> { private var value: Value private let range: ClosedRange<Value> … - 15
A colleague adds an
if letand aforloop to your custom @resultBuilder DSL and it stops compiling — what is missing?MediumA result builder supports exactly the control flow whose build methods you implemented:
ifwithout else needsbuildOptional,if/elseandswitchneedbuildEither, and `for ...struct Step { let title: String } @resultBuilder enum StepBuilder { static func buildBlock(_ parts: [Step]...) -> [Step] { parts.flatMap { $0 } } … - 16
How can Logger accept an interpolation with extra arguments, as in "user (id, privacy: .private)", when String interpolation takes none?
MediumBecause the argument is not a
Stringat all — it is a type conforming toExpressibleByStringInterpolationwhose interpolation type declares its ownappendInterpolationoverloads, and each\(...)is resolved as a normal call to one of them.import Foundation // A query type where interpolated values become bound parameters, never SQL text. struct SQLQuery: ExpressibleByStringInterpolation { let sql: String let bindings: [any Encodable] … - 17
A generic helper was fast inside the app target and visibly slower after you moved it into a package — what changed?
HardInside one module the optimiser specialises a generic function for every concrete type it sees called with; across a module boundary the body is not available to the client, so it links against the unspecialised version.
// ---- Module: Analytics (a separate package target) ---------------------- public struct Bucketer<Key: Hashable> { // An inlinable body may only reference public or @usableFromInline symbols. @usableFromInline let capacity: Int … - 18
You need a wrapper around a raw file descriptor that cannot be copied, double-closed or used after closing — what does ~Copyable give you?
HardSuppressing
Copyableremoves the implicit copy, so the value has exactly one owner at any moment and the compiler — not code review — proves that close happens once.import Darwin struct FileDescriptor: ~Copyable { private let fd: Int32 init?(opening path: String) { … - 19
Your reactive layer has six copy-pasted combineLatest overloads for two through seven inputs — how do parameter packs collapse them into one?
HardDeclare the generic parameter as a pack with
each, then expand it withrepeatwherever a list of types or values belongs, and one declaration covers every arity.import Foundation struct Store<Value> { private(set) var value: Value } … - 20
A settings screen wants rows of (title, key path), but an array of WritableKeyPaths with different value types will not compile — what do you store instead?
HardErasing a key path erases its
Valuetype as well, and the erased forms are read-only — so you keep the typed key path but capture it in a closure, or keep the value type in an enum case.struct Settings { var isDarkMode = false var fontSize = 14 var username = "" } …