Skip to content

Swift チートシート

iOS、macOS、そしてその先のための Apple の言語。

01

基礎

変数と定数

変更されない値にはデフォルトで let を使用し、変更が必要な場合のみ var に切り替えてください。Swift はコンパイル時に型を推論しますが、複雑な型や曖昧な型には明示的なアノテーションが可読性を向上させます。

swift
var name = "Alice"        // mutable
let age = 30               // immutable constant
let pi: Double = 3.14159   // explicit type
var count: Int = 0
count = 10
print(type(of: age))       // Int

オプショナル

オプショナルは値の不在を表します。安全なアンラップに if-let を、デフォルト値に ?? を使用し、! は値が確実に存在すると確信できる場合のみ使用してください(リスクあり—クラッシュする可能性)。

swift
var nickname: String? = nil
nickname = "Al"
if let n = nickname {
    print("Hi, \(n)")
}
print(nickname ?? "Anonymous")  // nil-coalescing
let force: String = nickname!   // force unwrap (crashes if nil)

タプル

タプルは複数の値を1つの複合値にグループ化します。関数から複数の値を返すのに有用です。名前付き要素は可読性を向上させます。

swift
let person = (name: "Alice", age: 30)
print(person.name)   // Alice
print(person.0)      // Alice
let (n, a) = person
print("\(n), \(a)")  // Alice, 30

型変換

Swift は型間の暗黙的な変換を行いません。曖昧さを避け、微妙なバグを防ぐため、常に明示的な変換(例:Double(intVal))を使用してください。

swift
let intVal = 42
let doubleVal = Double(intVal)
let strVal = String(intVal)
let fromStr = Int("100")!   // 100
let invalid = Int("abc")    // nil
print(doubleVal, strVal)

アサーションと事前条件

デバッグチェックには assert を(リリースビルドで削除)、すべてのビルドでチェックされる重要な不変条件には precondition を使用してください。どちらも論理エラーの早期発見に役立ちます。

swift
let age = -5
assert(age >= 0, "Age cannot be negative")
precondition(age >= 0, "Age must be non-negative")
// In debug builds, assert crashes if false
// precondition checks in release builds too
02

文字列と文字

文字列補間

\(expr) を使った文字列補間は任意の式を文字列に埋め込みます。タイプセーフでコンパイル時に評価されるため、フォーマット文字列より安全です。

swift
let name = "Alice"
let age = 30
let msg = "Name: \(name), Age: \(age)"
let calc = "5 + 3 = \(5 + 3)"
print(msg)        // Name: Alice, Age: 30
print(calc)       // 5 + 3 = 8

一般的な文字列メソッド

Swift の文字列は Unicode 正しいです。つまり count はバイト数ではなく実際の文字(書記素クラスタ)を反映します。安全のため手動のインデックス作成の代わりにこれらのメソッドを使用してください。

swift
let s = "Hello, World"
print(s.count)              // 12
print(s.uppercased())       // HELLO, WORLD
print(s.lowercased())       // hello, world
print(s.hasPrefix("Hello")) // true
print(s.contains("World"))  // true
print(s.reversed())         // "dlroW ,olleH"

部分文字列とインデックス

文字列のインデックスは整数ではありません。文字のバイトサイズが異なる可能性があるためです。ナビゲーションには index(_:offsetBy:) を使用してください。部分文字列は元の文字列とメモリを共有します—長期保存には String に変換してください。

swift
let s = "Hello, World"
let idx = s.index(s.startIndex, offsetBy: 7)
print(s[idx])                     // W
let sub = s[idx...]               // "World"
let newStr = String(sub)          // convert to String
print(s.prefix(5))                // "Hello"

複数行文字列

三重引用符文字列は改行とインデントを保持します。閉じる """ がベースラインインデントを決定します。HTML、JSON、長いテキストブロックに最適です。

swift
let poem = """
Roses are red,
Violets are blue,
Swift is great,
And so are you.
"""
print(poem)
// Use \\(expr) for interpolation in multiline

文字列の構築と分割

区切り文字で連結するには joined(separator:) を、トークン化するには split(separator:) を使用してください。これらは + 連結の手動ループより効率的です。

swift
let parts = ["apple", "banana", "cherry"]
let joined = parts.joined(separator: ", ")
print(joined)  // apple, banana, cherry
let csv = "a,b,c"
let fields = csv.split(separator: ",")
print(fields)  // ["a", "b", "c"]
let reversed = String(s.reversed())
03

データ構造

配列

配列は順序付きのゼロインデックスコレクションです。追加には append/insert を、変換には filter/map/reduce を使用します。スレッド安全性のため値型(Array は構造体)を優先してください。

swift
var nums = [1, 2, 3]
nums.append(4)
nums.insert(0, at: 0)
nums[1] = 20
print(nums.count)        // 5
print(nums.contains(20)) // true
let evens = nums.filter { $0 % 2 == 0 }
print(nums.first, nums.last)

ディクショナリ

ディクショナリはキーと値のペアを O(1) の平均ルックアップで格納します。存在しないキーへのアクセスで nil を避けるため default: を使用してください。キーは Hashable である必要があります。

swift
var ages: [String: Int] = ["Alice": 30]
ages["Bob"] = 25
ages["Alice"] = 31
print(ages["Alice", default: 0])  // 31
for (name, age) in ages {
    print("\(name): \(age)")
}
print(ages.keys.sorted())

セット

セットは O(1) のメンバーシップテストでユニークな値を格納します。重複排除とセット操作(和集合、積集合、差集合)に最適です。要素は Hashable である必要があります。

swift
var a: Set<Int> = [1, 2, 3]
var b: Set<Int> = [3, 4, 5]
print(a.union(b))           // {1,2,3,4,5}
print(a.intersection(b))    // {3}
print(a.subtracting(b))     // {1,2}
print(a.isSubset(of: b))    // false
a.insert(6)

範囲

..< は半開範囲(上限を含まない)、... は閉範囲(両方を含む)です。範囲はループ、スライシング、~= 演算子でのパターンマッチングに有用です。

swift
for i in 0..<5 { print(i) }   // 0,1,2,3,4
for i in 0...5 { print(i) }   // 0,1,2,3,4,5
let nums = Array(1...5)       // [1,2,3,4,5]
let prefix = nums.prefix(3)   // [1,2,3]
if 5...10 ~= 7 { print("in range") }

高階関数

map は各要素を変換、filter はマッチする要素を選択、reduce はすべてを1つに結合します。これらは Swift の関数型プログラミングの基盤であり、簡潔で読みやすいデータパイプラインを可能にします。

swift
let nums = [1, 2, 3, 4, 5]
let doubled = nums.map { $0 * 2 }       // [2,4,6,8,10]
let evens = nums.filter { $0 % 2 == 0 } // [2,4]
let sum = nums.reduce(0, +)             // 15
let strs = nums.map { String($0) }      // ["1","2",...]
print(doubled, evens, sum)
04

制御フロー

If / Else If / Else

標準的な条件分岐です。条件はブール(Bool 型)である必要があります。Swift は Apple の goto fail 脆弱性のようなバグを防ぐため、単一文でも中括弧を要求します。

swift
let score = 85
if score >= 90 {
    print("A")
} else if score >= 80 {
    print("B")
} else if score >= 70 {
    print("C")
} else {
    print("F")
}

Switch(パターンマッチング)

Swift の switch は強力です:値バインディング、タプル、where ガード、範囲をサポートします。網羅的である必要があり(default が残りのケースをカバー)、デフォルトでフォールスルーしません。

swift
let point = (2, 0)
switch point {
case (0, 0):
    print("origin")
case (_, 0):
    print("on x-axis")
case (0, _):
    print("on y-axis")
case let (x, y) where x == y:
    print("on diagonal")
default:
    print("elsewhere: \(point.0),\(point.1)")
}

For-In ループ

for-in は範囲、配列、ディクショナリ、任意の Sequence を反復します。インデックスが必要な場合は enumerated() を使用してください。使用しないループ変数は _ で無視できます。

swift
for i in 0..<5 { print(i) }
let fruits = ["apple", "banana"]
for (i, fruit) in fruits.enumerated() {
    print("\(i): \(fruit)")
}
let dict = ["a": 1, "b": 2]
for (k, v) in dict { print("\(k)=\(v)") }
for _ in 0..<3 { print("tick") }

While と Repeat-While

while は各反復前に条件をチェックし、repeat-while は後にチェックします(C の do-while のようなもの)。本体を少なくとも1回実行する必要がある場合は repeat-while を使用してください。

swift
var n = 5
while n > 0 {
    print(n)
    n -= 1
}
var x = 0
repeat {
    x += 1
} while x < 3
print(x)  // 3

Guard(早期退出)

guard は前提条件が満たされない場合の早期退出を提供します。アンラップされたオプショナルはスコープの残りで利用可能で、ネストを減らします。深い if-let ネストより guard を優先してください。

swift
func greet(_ name: String?) {
    guard let n = name, !n.isEmpty else {
        print("No name provided")
        return
    }
    print("Hello, \(n)")
    // n is unwrapped and available here
}
greet("Alice")
greet(nil)
05

関数とクロージャ

関数定義

可読性のために引数ラベルを省略するには _ を使用します。デフォルトパラメータ値でパラメータをオプションにします。単一式の関数は暗黙的な return を持ちます(Swift 5.9 以降)。

swift
func greet(_ name: String, greeting: String = "Hello") -> String {
    return "\(greeting), \(name)!"
}
print(greet("Alice"))               // Hello, Alice!
print(greet("Bob", greeting: "Hi")) // Hi, Bob!
func add(_ a: Int, _ b: Int) -> Int { a + b }
print(add(3, 4))  // 7

複数の戻り値

タプルで複数の名前付き値を返せます。オプショナルタプルを返すことで失敗の可能性を示せます。r.min または r.0 でアクセス—名前付き要素の方が明確です。

swift
func minMax(_ nums: [Int]) -> (min: Int, max: Int)? {
    guard let first = nums.first else { return nil }
    var mn = first, mx = first
    for n in nums { mn = min(mn, n); mx = max(mx, n) }
    return (mn, mx)
}
if let r = minMax([3, 1, 4, 1, 5]) {
    print("min=\(r.min), max=\(r.max)")
}

クロージャ

クロージャは自己完結した機能ブロックです。$0、$1 を省略引数名として使用します。後置クロージャ構文(最後の引数ラベルを省略)は map/filter/reduce で慣用的です。

swift
let square: (Int) -> Int = { x in x * x }
print(square(5))  // 25
let add: (Int, Int) -> Int = { $0 + $1 }
print(add(3, 4))  // 7
let nums = [1, 2, 3]
let doubled = nums.map { $0 * 2 }
print(doubled)  // [2, 4, 6]

Escaping と Auto-Closure

@escaping は関数が戻った後に保存または呼び出されるクロージャをマークします(非同期コールバックに必要)。@autoclosure は式をクロージャでラップし、評価を遅延させます(assert で使用)。

swift
var handlers: [() -> Void] = []
func saveHandler(_ fn: @escaping () -> Void) {
    handlers.append(fn)
}
saveHandler { print("done") }
handlers.first?()
// @autoclosure delays evaluation
func logIfTrue(_ cond: @autoclosure () -> Bool) {
    if cond() { print("true") }
}
logIfTrue(2 > 1)

inout パラメータ

inout パラメータは関数が呼び出し側の変数を変更できるようにします(参照渡し)。明確さのため新しい値を返すことを優先し、控えめに使用してください。& プレフィックスが変更箇所を示します。

swift
func swap(_ a: inout Int, _ b: inout Int) {
    let temp = a
    a = b
    b = temp
}
var x = 10, y = 20
swap(&x, &y)
print(x, y)  // 20 10
06

クラスと構造体

構造体(値型)

構造体は値型です—代入時にコピーされます。シンプルなデータコンテナに使用します。計算プロパティ(distance)はアクセス時に計算します。mutating メソッドが self を変更します。

swift
struct Point {
    var x: Double
    var y: Double
    var distance: Double {
        (x * x + y * y).squareRoot()
    }
    mutating func moveBy(dx: Double, dy: Double) {
        x += dx; y += dy
    }
}
var p = Point(x: 3, y: 4)
print(p.distance)  // 5.0

クラス(参照型)

クラスは参照型です—参照経由で共有、継承と deinit をサポートします。アイデンティティ、共有された可変状態、または Objective-C 相互運用性が必要な場合に使用します。それ以外は構造体を優先してください。

swift
class Person {
    var name: String
    var age: Int
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
    deinit { print("\(name) deallocated") }
}
let p = Person(name: "Alice", age: 30)
print(p.name)  // Alice

継承とオーバーライド

スーパークラスのメソッドを再定義するには override を使用します。Swift は動的ディスパッチを使用するためサブクラスのバージョンが呼ばれます。パフォーマンスのためメソッドに final を付けてさらなるオーバーライドを防いでください。

swift
class Animal {
    func speak() { print("...") }
}
class Dog: Animal {
    override func speak() { print("Woof!") }
}
class Cat: Animal {
    override func speak() { print("Meow!") }
}
let pets: [Animal] = [Dog(), Cat()]
for pet in pets { pet.speak() }

プロパティ(計算と遅延)

計算プロパティは get/set ブロックを持ち、set はデフォルトで newValue を使用します。lazy は初回アクセスまで初期化を遅延します—高コストや稀に必要なプロパティに有用です。var である必要があります。

swift
class Circle {
    var radius: Double
    var area: Double {
        get { .pi * radius * radius }
        set { radius = (newValue / .pi).squareRoot() }
    }
    lazy var heavy = loadExpensiveData()
    init(radius: Double) { self.radius = radius }
}
func loadExpensiveData() -> String { "data" }

プロパティオブザーバー

willSet/didSet がプロパティの変更を監視します。didSet で値の検証や変更への反応(例:値のクランプ、UI 更新のトリガー)を行ってください。オブザーバーは init 中は発火しません。

swift
class Counter {
    var count: Int = 0 {
        willSet { print("about to set \(newValue)") }
        didSet {
            print("was \(oldValue), now \(count)")
            if count > 10 { count = 10 }
        }
    }
}
let c = Counter()
c.count = 5
c.count = 20  // clamped to 10
07

プロトコルと拡張

プロトコル定義

プロトコルはメソッドとプロパティの設計図を定義します。型はそれらを実装することで準拠します。抽象化、ポリモーフィズム、疎結合に使用します—Java/C# のインターフェースに似ています。

swift
protocol Greetable {
    var name: String { get }
    func greet() -> String
}
struct User: Greetable {
    let name: String
    func greet() -> String { "Hi, \(name)" }
}
let u = User(name: "Alice")
print(u.greet())  // Hi, Alice

プロトコル拡張(デフォルト実装)

プロトコル拡張はデフォルト実装を提供します。これにより遡的モデリングと基底クラスなしのコード再利用が可能になります。無関係な型間で振る舞いを共有する強力な機能です。

swift
protocol Describable {
    func describe() -> String
}
extension Describable {
    func describe() -> String { "A \(type(of: self))" }
}
struct Box: Describable { }
let b = Box()
print(b.describe())  // A Box

拡張

拡張は既存の型(Int のような所有しない型も含む)に機能を追加します。コードの整理、計算プロパティの追加、プロトコルへの準拠に使用します。格納プロパティは追加できません。

swift
extension Int {
    var squared: Int { self * self }
    func times(_ block: () -> Void) {
        for _ in 0..<self { block() }
    }
}
print(5.squared)  // 25
3.times { print("hi") }  // prints hi 3 times

ジェネリクス

ジェネリクスは型安全性を保ちながら任意の型で動作する柔軟で再利用可能なコードを記述します。型パラメータに <T> を使用します。制約(where T: Equatable)で許可される型を制限します。

swift
func stackOf<T>(_ items: T...) -> [T] {
    var arr: [T] = []
    for item in items { arr.append(item) }
    return arr
}
struct Stack<Element> {
    private var items: [Element] = []
    mutating func push(_ e: Element) { items.append(e) }
}
let s = stackOf(1, 2, 3)  // [Int]

関連型を持つプロトコル

関連型によりプロトコルはプレースホルダー型を使用できます。プロトコル用のジェネリクスのようなものです。準拠する型が実際の型を指定します。明示的にするには typealias を使用するか、Swift に推論させます。

swift
protocol Container {
    associatedtype Item
    var count: Int { get }
    mutating func append(_ item: Item)
    subscript(i: Int) -> Item { get }
}
struct IntStack: Container {
    typealias Item = Int
    private var items: [Int] = []
    var count: Int { items.count }
    mutating func append(_ item: Int) { items.append(item) }
    subscript(i: Int) -> Int { items[i] }
}
08

エラー処理

エラーの定義とスロー

エラーは Error プロトコルに準拠します(通常は列挙型)。throw がエラーを通知します。関連値(coinsNeeded)がコンテキストを運びます。失敗する可能性のある関数に throws をマークします。

swift
enum VendingError: Error {
    case invalidSelection
    case insufficientFunds(coinsNeeded: Int)
    case outOfStock
}
func vend(item: String, coins: Int) throws -> String {
    guard item == "Candy" else { throw VendingError.invalidSelection }
    guard coins >= 2 else { throw VendingError.insufficientFunds(coinsNeeded: 2) }
    return "Dispensing \(item)"
}

Do-Catch と Try

do-catch がスローされたエラーを処理します。try がスローする呼び出しをマークします。対象を絞った処理のために特定のケースでパターンマッチします。予期しないエラーを処理する catch-all もあります。エラーはコールスタックを伝播します。

swift
do {
    let result = try vend(item: "Candy", coins: 1)
    print(result)
} catch VendingError.insufficientFunds(let needed) {
    print("Need \(needed) more coins")
} catch VendingError.outOfStock {
    print("Sold out")
} catch {
    print("Other error: \(error)")
}

Try? / Try!

try? はエラーを nil に変換します(Optional を返します)。try! は成功を前提としエラー時にクラッシュします—失敗が本当に不可能な場合のみ使用してください。優雅な降格のために try? とオプショナルバインディングを優先してください。

swift
let result1 = try? vend(item: "Candy", coins: 1)
print(result1)  // nil (error converted to optional)
let result2 = try! vend(item: "Candy", coins: 5)
print(result2)  // Dispensing Candy (crashes if error)
if let r = try? vend(item: "Candy", coins: 5) {
    print(r)
}

Result 型

Result は成功または失敗を値としてエンコードし、throw なしで非同期エラー処理を可能にします。コールバック、非同期 API、結果を保存/チェーンする場合に使用します。.get() は失敗時にスローします。

swift
enum FetchError: Error { case network, parse }
func fetch(_ url: String) -> Result<String, FetchError> {
    url.isEmpty ? .failure(.network) : .success("data from \(url)")
}
switch fetch("https://api.com") {
case .success(let data): print(data)
case .failure(let err): print("Error: \(err)")
}
let value = try? fetch("").get()

Defer(クリーンアップ)

defer はスコープ退出時に実行するクリーンアップコードをスケジュールします(通常の return、throw、エラーに関わらず)。リソース解放(ファイル、ロック)に使用します。複数の defer は LIFO 順で実行されます。

swift
func processFile(_ path: String) throws {
    let handle = openFile(path)
    defer { closeFile(handle) }
    // ... work that might throw ...
    let data = try read(handle)
    // closeFile always runs, even if throw
    print("processed \(data)")
}
09

ファイル I/O と日付/時刻

ファイルの読み書き

Foundation はシンプルなファイル API を提供します。write(toFile:atomically:) は安全に書き込みます(一時ファイル + リネーム)。テキストには String(contentsOfFile:) を使用します。大きなファイルにはストリーミング用に FileHandle を使用してください。

swift
import Foundation
let path = "test.txt"
let content = "Hello, File I/O!"
try content.write(toFile: path, atomically: true,
                  encoding: .utf8)
let read = try String(contentsOfFile: path, encoding: .utf8)
print(read)  // Hello, File I/O!
let lines = read.split(separator: "\n")
print(lines.count)

URL と FileManager

FileManager はファイルシステム操作(作成、削除、存在確認)を処理します。モダンな API にはパスではなく URL を使用してください。.documentDirectory は iOS/macOS でのアプリの永続ストレージです。

swift
import Foundation
let fm = FileManager.default
let url = fm.urls(for: .documentDirectory, in: .userDomainMask)[0]
    .appendingPathComponent("data.json")
try "data".write(to: url, atomically: true, encoding: .utf8)
let exists = fm.fileExists(atPath: url.path)
print("Exists: \(exists)")
try fm.removeItem(at: url)  // delete

Date と DateFormatter

Date は時点を表します(内部的には UTC)。DateFormatter は Date と String 間の変換を行います—ロケールバグを避けるため固定フォーマット解析には常に locale を en_US_POSIX に設定してください。

swift
import Foundation
let now = Date()
let fmt = DateFormatter()
fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
fmt.locale = Locale(identifier: "en_US_POSIX")
let str = fmt.string(from: now)
print(str)  // e.g. 2024-01-15 14:30:00
let parsed = fmt.date(from: "2024-01-01 00:00:00")
print(parsed ?? "invalid")

JSON エンコード/デコード

Codable が JSON シリアライズを自動化します。JSONEncoder/JSONDecoder が変換を処理します。型を Codable に準拠させてください—コンパイラがロジックを合成します。キー名をカスタマイズするには CodingKeys を使用します。

swift
struct User: Codable {
    let name: String
    let age: Int
}
let user = User(name: "Alice", age: 30)
let json = try JSONEncoder().encode(user)
let str = String(data: json, encoding: .utf8)!
print(str)  // {"name":"Alice","age":30}
let decoded = try JSONDecoder().decode(User.self, from: json)
print(decoded.name)  // Alice

日付計算

Calendar はタイムゾーンと DST を考慮した日付計算を処理します。フィールドの抽出や差分の計算には dateComponents を使用してください。日付計算に生の秒数は使用しないでください—Calendar API を使用してください。

swift
import Foundation
let cal = Calendar.current
let now = Date()
let tomorrow = cal.date(byAdding: .day, value: 1, to: now)!
let components = cal.dateComponents([.year, .month, .day], from: now)
print(components.year!, components.month!)
let diff = cal.dateComponents([.hour], from: now, to: tomorrow)
print(diff.hour!)  // 24
10

並行性と非同期

Async / Await

async/await(Swift 5.5 以降)は非同期コードを同期コードのように読めるようにします。Task が新しい非同期コンテキストを作成します。async let が並行実行し await ですべてを待機します。コールバック地獄を排除—コンプリーションハンドラより優先してください。

swift
func fetchUser(_ id: Int) async -> String {
    try? await Task.sleep(nanoseconds: 1_000_000_000)
    return "User \(id)"
}
Task {
    let user = await fetchUser(42)
    print(user)  // User 42
    async let u1 = fetchUser(1)
    async let u2 = fetchUser(2)
    let users = await [u1, u2]  // concurrent
    print(users)
}

Task とキャンセル

Task は非同期作業の単位を表します。Task.isCancelled による協調的キャンセル—長時間実行タスクは定期的にチェックすべきです。task.value が結果を待機します。キャンセルは強制ではなく協調的です。

swift
let task = Task {
    for i in 1...100 {
        if Task.isCancelled { return }
        try? await Task.sleep(nanoseconds: 100_000_000)
        print(i)
    }
}
Task {
    try? await Task.sleep(nanoseconds: 500_000_000)
    task.cancel()  // request cancellation
}
await task.value  // wait for completion

アクター(スレッド安全性)

アクター(Swift 5.5 以降)はアクセスを直列化してデータ競合から可変状態を保護します。すべてのアクセスは await を通じます。共有状態にロック/キューの代わりに使用してください。コンパイラが安全性を検証します。

swift
actor Counter {
    private var count = 0
    func increment() { count += 1 }
    func value() -> Int { count }
}
let counter = Counter()
Task {
    await counter.increment()
    print(await counter.value())
}
// Actors serialize access—no data races

GCD(Grand Central Dispatch)

GCD は従来の並行性 API です。バックグラウンド作業に DispatchQueue.global() を、UI 更新に .main を使用します。QoS(.userInitiated、.background)がタスクの優先度を決定します。非非同期コードには依然として有用です。

swift
import Dispatch
DispatchQueue.global(qos: .userInitiated).async {
    let result = heavyComputation()
    DispatchQueue.main.async {
        print("UI update: \(result)")
    }
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
    print("delayed by 2s")
}

Async Sequence

AsyncSequence は非同期値(ストリームのようなもの)の反復を可能にします。消費には for await を使用します。ページネーションされた API、サーバー送信イベント、時間とともに値を生成する任意のソースに最適です。

swift
let urls = ["url1", "url2", "url3"]
func fetchAll() async {
    for url in urls {
        let data = await fetchData(url)
        print(data)
    }
}
func fetchData(_ url: String) async -> String {
    "data from \(url)"
}
// AsyncSequence: for await item in stream { ... }
11

プロトコル 深掘り

関連型を持つプロトコル

関連型(associatedtype)によりプロトコルは準拠する型が指定するプレースホルダー型を宣言できます。これはプロトコル用のジェネリック型パラメータの Swift 版です。型は準拠する型のメソッドから推論されます。'where' 句で関連型を制約します。PAT(関連型を持つプロトコル)は型消去または(Swift 5.7 以降)'any Container' なしでは存在型として直接使用できません。

swift
protocol Container {
    associatedtype Item
    var count: Int { get }
    mutating func append(_ item: Item)
    subscript(i: Int) -> Item { get }
}

struct IntStack: Container {
    // associatedtype Item inferred as Int
    private var items: [Int] = []
    var count: Int { items.count }
    mutating func append(_ item: Int) { items.append(item) }
    subscript(i: Int) -> Int { items[i] }
}

// Generic constraint with associated type
func sum<C: Container>(_ c: C) -> Int where C.Item == Int {
    var total = 0
    for i in 0..<c.count { total += c[i] }
    return total
}

プロトコル拡張(デフォルト実装)

プロトコル拡張はデフォルト実装を提供します—準拠する型はメソッドを無料で取得できますが、オーバーライドも可能です。これが Swift の型に遡的に機能を追加する方法です。制約付き拡張(where Element: Numeric)は制約を満たす型にのみメソッドを追加します。これが標準ライブラリがすべての Collection に map/filter/reduce を追加する方法です。

swift
protocol Describable {
    var description: String { get }
}

// Default implementation via extension
extension Describable {
    var description: String { "A \(type(of: self))" }
}

struct Point: Describable {
    let x, y: Int
    // Uses default description: "A Point"
}

struct Person: Describable {
    let name: String
    var description: String { "Person named (name)" }  // override
}

// Extension with constraints
extension Collection where Element: Numeric {
    var sum: Element { reduce(0, +) }
}
[1, 2, 3].sum  // 6

プロトコル合成と存在型

プロトコル合成(A & B)は値が複数のプロトコルに準拠することを要求します。存在型(any Protocol)は任意の準拠する型を保持できますが、呼び出しごとのウィットネステーブルルックアップの実行時ディスパッチオーバーヘッドがあります。1つの特定の型を返すが非表示にしたい場合は 'some Protocol'(不透明戻り値型)を使用します。パフォーマンスのため存在型よりジェネリクスを優先し、異種コレクションが必要な場合に存在型を使用してください。

swift
protocol Named { var name: String { get } }
protocol Aged { var age: Int { get } }

struct Person: Named, Aged {
    let name: String
    let age: Int
}

// Protocol composition: requires both protocols
func wishHappyBirthday(to celebrant: Named & Aged) {
    print("Happy birthday, (celebrant.name), you're (celebrant.age)!")
}

let p = Person(name: "Alice", age: 30)
wishHappyBirthday(to: p)

// Existential types (any) — type erasure overhead
let items: [any Named] = [p, Pet(name: "Rex")]
for item in items { print(item.name) }

// Swift 5.7: 'any' keyword required for existentials
// 'some' for opaque types (returns one concrete type)

プロトコル指向プログラミング

プロトコル指向プログラミングは OOP からの Swift のパラダイムシフトです。クラス階層の代わりに、デフォルト実装を持つプロトコルを中心に設計します。利点:値型(構造体/列挙型)で動作、遡的準拠をサポート(所有しない型を拡張)、複数の「継承」を可能に(型は多くのプロトコルに準拠可能)。参照セマンティクスと Objective-C 相互運用にはクラスが依然として有用ですが、ほとんどのモデルには構造体+プロトコルが推奨されます。

swift
// POP: design around protocols, not inheritance
protocol Drawable {
    func draw(on canvas: Canvas)
}

extension Drawable {
    // Default implementation + polymorphism
    func drawTwice(on canvas: Canvas) {
        draw(on: canvas)
        draw(on: canvas)
    }
}

struct Circle: Drawable { let radius: Double
    func draw(on canvas: Canvas) { /* ... */ }
}
struct Square: Drawable { let side: Double
    func draw(on canvas: Canvas) { /* ... */ }
}

// Value types + protocols = no inheritance needed
let shapes: [any Drawable] = [Circle(radius: 5), Square(side: 3)]
shapes.forEach { $0.draw(on: canvas) }

// Retroactive modeling (extend types you don't own)
extension Int: Drawable {
    func draw(on canvas: Canvas) { /* draw the number */ }
}

カスタムプロトコルウィットネス

依存関係としてのプロトコル(Repository)により、テストと柔軟性のために実装を差し替え(実装 vs モック)できます。関連型によりプロトコルがジェネリックになります。これが Swift の依存性注入です—プロトコル準拠型を注入します。「プロトコルウィットネス」は具体的な振る舞いを提供する準拠型です。このパターン(リポジトリ、データソース)は Swift アーキテクチャ(VIPER、Clean Architecture)で一般的です。

swift
// Protocol with requirements
protocol Repository {
    associatedtype Entity
    func get(_ id: Int) -> Entity?
    func save(_ entity: Entity)
}

// Concrete implementation
struct UserRepo: Repository {
    typealias Entity = User
    func get(_ id: Int) -> User? { /* fetch */ nil }
    func save(_ user: User) { /* persist */ }
}

// Generic function using protocol
func display<R: Repository>(_ repo: R, id: Int) where R.Entity: CustomStringConvertible {
    if let entity = repo.get(id) {
        print(entity.description)
    }
}

// Test double for testing
struct MockUserRepo: Repository {
    typealias Entity = User
    var stored: [Int: User] = [:]
    func get(_ id: Int) -> User? { stored[id] }
    func save(_ user: User) { /* no-op for tests */ }
}
12

ジェネリクス 深掘り

ジェネリック関数と型

ジェネリクスは型安全性を保ちながら任意の型で動作する柔軟で再利用可能なコードを記述します。T は型パラメータ(プレースホルダー)です。コンパイラはパフォーマンスのために特殊化されたバージョンを生成します(ボックス化なし)。ジェネリック型(Stack<T>)は型パラメータを維持します。Element は Array のジェネリック型です。ジェネリクスはコンパイル時に解決され—存在型とは異なり実行時オーバーヘッドはありません。

swift
// Generic function
func swap<T>(_ a: inout T, _ b: inout T) {
    let temp = a; a = b; b = temp
}
var x = 1, y = 2
swap(&x, &y)  // x=2, y=1

// Generic type
struct Stack<T> {
    private var items: [T] = []
    mutating func push(_ item: T) { items.append(item) }
    mutating func pop() -> T? { items.popLast() }
}

var intStack = Stack<Int>()
intStack.push(42)
var strStack = Stack<String>()
strStack.push("hello")

// Generic method on non-generic type
extension Array {
    func chunked(into size: Int) -> [[Element]] {
        stride(from: 0, to: count, by: size).map {
            Array(self[$0..<Swift.min($0 + size, count)])
        }
    }
}

型制約

型制約は使用できる型を制限します:T: Equatable は T がプロトコルに準拠することを要求し、T: SomeClass はクラス階層を要求します。where 句はより複雑な制約(例:関連型のマッチング)を追加します。制約によりプロトコルのメソッド(Equatable の ==、Comparable の <)を使用できます。制約なしでは T を代入して渡すことしかできません—操作はできません。

swift
// Constraint: T must conform to Equatable
func indexOf<T: Equatable>(_ item: T, in array: [T]) -> Int? {
    for (i, elem) in array.enumerated() {
        if elem == item { return i }  // needs Equatable
    }
    return nil
}

// Multiple constraints
func max<T: Comparable>(_ a: T, _ b: T) -> T {
    return a > b ? a : b
}

// Constraint on associated type
protocol Repository {
    associatedtype Entity: Identifiable
    func find(_ id: Entity.ID) -> Entity?
}

// where clause for complex constraints
func merge<C1: Collection, C2: Collection>(
    _ c1: C1, _ c2: C2
) -> [C1.Element] where C1.Element == C2.Element {
    Array(c1) + Array(c2)
}

不透明型(some)

不透明型(some Protocol、Swift 5.1)は呼び出し側から隠された特定の具象型を返します。'any'(存在型)とは異なり、型は固定されコンパイラに既知です—ボックス化なし、コンパイル時ディスパッチ。これが SwiftUI の 'some View' の基盤です。具象型を非表示にしつつパフォーマンスを維持したい場合に 'some' を使用してください。呼び出し側はプロトコルメソッドを使用できますが、特定の型に依存できません。

swift
// Opaque return type: returns ONE concrete type
// (hidden from caller, but fixed per call site)
func makeStack() -> some Collection<Int> {
    return [1, 2, 3]  // concrete type is [Int]
}

let s = makeStack()  // type is 'some Collection<Int>'
print(s.count)  // 3

// Useful for hiding implementation details
protocol Shape { func draw() }
struct Circle: Shape { func draw() { print("circle") } }

func makeShape() -> some Shape {
    return Circle()  // caller doesn't know it's Circle
}

// Difference from existential (any):
// - some: one concrete type, no boxing, compile-time dispatch
// - any: can be different types, runtime dispatch, boxing

// Opaque types in properties
struct View {
    var body: some View { Text("Hello") }  // SwiftUI
}

条件付き準拠

条件付き準拠は型パラメータが特定の条件を満たす場合のみ型をプロトコルに準拠させます。Array<Int> は Int が Equatable なので Equatable です。Array<MyStruct> は MyStruct がそうでなければそうではありません。これは伝播します:[[Int]] は [Int] がそうなので Equatable です。標準ライブラリはこれを広範に使用します—Array、Optional、Dictionary はすべて条件付きで Equatable、Hashable、Codable に準拠します。

swift
// Array conforms to Equatable only if Element is Equatable
extension Array: Equatable where Element: Equatable {
    static func == (lhs: [Element], rhs: [Element]) -> Bool {
        return lhs.count == rhs.count && lhs.elementsEqual(rhs)
    }
}

[1, 2, 3] == [1, 2, 3]  // true (Int: Equatable)
// [SomeStruct] == [SomeStruct]  // error if SomeStruct isn't Equatable

// Conditional conformance for Hashable
extension Stack: Equatable where T: Equatable {
    static func == (lhs: Stack, rhs: Stack) -> Bool {
        return lhs.items == rhs.items
    }
}

// Enables: [Stack<Int>] == [Stack<Int>] (transitively)

Result とジェネリックエラー処理

Result<Success, Failure> は失敗する可能性のある操作用のジェネリック列挙型で、値またはエラーのいずれかを運びます。エラー処理を遅延させたり結果を保存したい場合に有用です。map は成功値を変換し、flatMap は失敗する可能性のある操作をチェーンします。Result { try ... } がスローする関数を変換します。.get() はスローに戻って変換します。async/await の場合、Result は不要になることが多いですが、保存/渡されるエラーには依然として有用です。

swift
// Result: built-in generic for success/failure
enum Result<Success, Failure: Error> {
    case success(Success)
    case failure(Failure)
}

func fetchUser(_ id: Int) -> Result<User, FetchError> {
    if id < 0 { return .failure(.invalidId) }
    return .success(User(id: id))
}

switch fetchUser(42) {
case .success(let user): print(user)
case .failure(let err): print(err)
}

// map/flatMap for chaining
let result = fetchUser(1)
    .map { $0.name }
    .flatMap { name in fetchAvatar(name) }

// Throwing function → Result
let result = Result { try throwingFunction() }
// Result → throwing
let value = try result.get()
13

ARC とメモリ管理

自動参照カウント(ARC)

ARC(Automatic Reference Counting)はクラスインスタンス(参照型)のメモリを管理します。各強参照がカウントをインクリメントし、削除するとデクリメントします。カウントが0になるとオブジェクトは即座に解放されます(ガベージコレクションとは異なり決定的)。構造体/列挙型(値型)は ARC を使用しません—コピーされます。ARC は参照サイクルを処理できません—weak/unowned で破る必要があります。

swift
class Person {
    let name: String
    init(name: String) { self.name = name; print("\(name) initialized") }
    deinit { print("\(name) deinitialized") }
}

var p1: Person? = Person(name: "Alice")  // "Alice initialized"
var p2 = p1  // strong ref count = 2
p1 = nil     // ref count = 1, not deinitialized
p2 = nil     // ref count = 0, "Alice deinitialized"

// ARC tracks strong references:
// - +1 on assignment/copy
// - -1 when variable goes out of scope
// - Object freed when count hits 0
// Unlike GC, deterministic (freed immediately when count=0)

強、弱、非所有参照

強参照(デフォルト)はオブジェクトを存続させます。weak 参照はオブジェクトを存続させず、オブジェクトが解放されると nil になります(optional var である必要があります)。unowned 参照はオブジェクトを存続させませんが非オプショナルです—参照先のオブジェクトが参照より長く存続することを確信できる場合のみ使用してください(解放後にアクセスするとクラッシュ)。デリゲートパターンには weak を、親が常に子より長く存続する親子関係には unowned を使用してください。

swift
class Person {
    var name: String
    var apartment: Apartment?  // strong
    init(name: String) { self.name = name }
    deinit { print("\(name) deinit") }
}

class Apartment {
    var unit: String
    weak var tenant: Person?  // weak breaks cycle
    init(unit: String) { self.unit = unit }
    deinit { print("Apartment \(unit) deinit") }
}

var alice: Person? = Person(name: "Alice")
var apt: Apartment? = Apartment(unit: "4A")
alice?.apartment = apt
apt?.tenant = alice  // weak, doesn't keep alice alive

alice = nil  // "Alice deinit" (no strong cycle)
apt = nil    // "Apartment 4A deinit"

// unowned: non-optional, assumed to always have a value
// Use when the referenced object outlives the reference
class Customer {
    var card: CreditCard?  // strong
}
class CreditCard {
    unowned let owner: Customer  // non-nil, non-optional
    init(owner: Customer) { self.owner = owner }
}

保持サイクルとクロージャ

クロージャはデフォルトで参照を強くキャプチャします。クラスが self をキャプチャするクロージャを格納する場合、保持サイクルになります(両方が互いを存続させ→メモリリーク)。キャプチャリストで修正:[weak self](self がオプショナルになり、guard let を使用)または [unowned self](非オプショナル、nil でクラッシュ)。プロパティとして格納または長寿命オブジェクト(オブザーバー、非同期タスク)に渡されるクロージャでは常に weak self を使用してください。

swift
class ViewModel {
    var name = "VM"
    var onUpdate: (() -> Void)?

    func setup() {
        // BAD: closure captures self strongly → cycle
        onUpdate = {
            print(self.name)  // strong capture
        }
        // ViewModel holds closure, closure holds ViewModel → leak!

        // GOOD: capture list with weak self
        onUpdate = { [weak self] in
            guard let self = self else { return }
            print(self.name)
        }

        // GOOD: unowned if self outlives closure
        onUpdate = { [unowned self] in
            print(self.name)
        }
    }
    deinit { print("VM deinit") }
}

Escaping と非 Escaping クロージャ

非 escaping クロージャ(デフォルト)は関数内で実行され破棄されます—保持サイクルリスクなし、クロージャ内で self. が不要。@escaping クロージャは関数より長く存続します(プロパティに格納、非同期でディスパッチなど)—保持サイクルを引き起こす可能性があるため、self. を明示的に使用し [weak self] を検討する必要があります。コンパイラがこれを強制します。ほとんどのコンプリーションハンドラは @escaping です。map/filter/reduce は非 escaping です。

swift
class DataLoader {
    var storedHandler: (() -> Void)?

    // @escaping: closure outlives the function (stored or async)
    func load(with handler: @escaping (Data) -> Void) {
        storedHandler = { handler(cachedData) }  // stored
        DispatchQueue.global().async {
            handler(self.fetch())  // escapes via async
        }
    }

    // Non-escaping (default): closure runs during the function
    func process(_ transform: (Int) -> Int) -> [Int] {
        return [1, 2, 3].map(transform)  // runs now, no storage
    }
    // No need for [weak self]—self is alive during the call
}

// @escaping requires explicit self. in closures
class View {
    func load() {
        DataLoader().load { [self] data in  // self. required
            print(data)
        }
    }
}

メモリ安全性と排他性

Swift は排他性を強制します—変数は重複するスコープから同時にアクセス(読み取り+書き込み、書き込み+書き込み)できません。これによりデータ競合と未定義動作を防ぎます。inout パラメータは呼び出し期間中の排他アクセスを取得します。構造体の mutating メソッドは self への排他アクセスを保持します。アクター(Swift 5.5 以降)は共有された可変状態の言語レベルでの排他性を強制し、設計によるデータ競合を排除します。

swift
// Exclusivity: Swift prevents overlapping access to a variable
// This is a compile-time or runtime error:
var step = 1
func increment(_ n: inout Int) {
    step += 1  // accessing global while n is inout to step
}
// increment(&step)  // ERROR: overlapping access

// Fix with explicit copy
func safeIncrement(_ n: inout Int) {
    var copy = step
    copy += 1
    step = copy
}

// Struct mutating methods have exclusive access
struct Counter {
    var count = 0
    mutating func increment(_ other: inout Counter) {
        count += other.count  // ok, different instances
    }
}

// 'inout' parameters: exclusive access for duration
// 'isolation' (Swift 5.5+): actors enforce exclusivity
14

SwiftUI の基礎

ビューとモディファイア

SwiftUI は Apple の宣言的 UI フレームワークです。ビューは 'body' プロパティを持つ View に準拠する構造体です。モディファイア(.font、.padding)は新しいラップされたビューを返します—変更しません。ビューは軽量な値型で、SwiftUI が実際の UI を更新するために差分を取ります。宣言的スタイルは与えられた状態に対して UI がどう見えるべきかを記述し、SwiftUI が遷移を処理します。

swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack(spacing: 20) {
            Text("Hello, World!")
                .font(.title)
                .fontWeight(.bold)
                .foregroundColor(.blue)

            Button(action: { print("tapped") }) {
                HStack {
                    Image(systemName: "star.fill")
                    Text("Tap me")
                }
            }
            .padding()
            .background(Color.orange)
            .cornerRadius(10)
        }
        .padding()
    }
}

// Views are value types (structs), declarative
// Modifiers return new views (immutability)

State と Binding

@State はローカルで可変なビュー状態用です—変更されると SwiftUI がビューを再レンダリングします。@Binding は $ プレフィックス経由で子ビューが親の @State を読み取り/書き込みできるようにします(バインディングを作成)。状態は単一の真実の情報源であるべきで、変更用の値ではなくバインディングを下に渡します。ビュー間で共有/複雑な状態には ObservableObject と @StateObject/@ObservedObject/@EnvironmentObject を使用してください。

swift
struct CounterView: View {
    @State private var count = 0  // local, mutable state

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") { count += 1 }
        }
    }
}

// @Binding: child receives a reference to parent's state
struct StepperView: View {
    @Binding var value: Int
    var body: some View {
        HStack {
            Button("-") { value -= 1 }
            Text("\(value)")
            Button("+") { value += 1 }
        }
    }
}

// Parent passes binding with $
struct ParentView: View {
    @State var score = 0
    var body: some View {
        StepperView(value: $score)  // $ creates a Binding
    }
}

ObservableObject と EnvironmentObject

ObservableObject(クラス)は複数ビュー間の共有状態用です。@Published プロパティが変更されるとビュー更新をトリガーします。@StateObject がオブジェクトを作成・所有します(階層の最上位で使用)。@EnvironmentObject がオブジェクトをビューツリーに注入し—任意の子孫がバインディング経由で渡すことなくアクセスできます。@ObservedObject は既存のオブジェクトを受け取ります(所有しません)。所有権には @StateObject を、受け取りには @ObservedObject/@EnvironmentObject を使用してください。

swift
// Model: ObservableObject for shared state
class AppModel: ObservableObject {
    @Published var username: String = ""  // @Published triggers UI update
    @Published var isLoggedIn: Bool = false

    func login() {
        // ... auth logic ...
        isLoggedIn = true
    }
}

// @StateObject: owns the model (created once)
struct RootView: View {
    @StateObject var model = AppModel()
    var body: some View {
        // Inject into environment
        ContentView().environmentObject(model)
    }
}

// @EnvironmentObject: receives from environment
struct ProfileView: View {
    @EnvironmentObject var model: AppModel
    var body: some View {
        Text("Hello, \(model.username)")
    }
}

// @ObservedObject: receives from parent (doesn't own)
struct LoginView: View {
    @ObservedObject var model: AppModel
}

リストとナビゲーション

List はスクロール可能な行をレンダリングします(UITableView のようなもの)。アイテムは Identifiable に準拠する(または id: を提供する)必要があります。NavigationStack(iOS 16 以降)がナビゲーションを管理し、NavigationLink が宛先をプッシュします。LazyVStack/LazyHStack はパフォーマンスのためにコンテンツを遅延読み込みします。ForEach は他のコンテナ内でビューを繰り返すためのものです。表形式データには List を、カスタムレイアウトには ScrollView+LazyVStack を使用してください。

swift
struct Item: Identifiable {
    let id = UUID()
    let name: String
}

struct ListView: View {
    let items = [Item(name: "Apple"), Item(name: "Banana")]

    var body: some View {
        NavigationStack {
            List(items) { item in
                NavigationLink(item.name) {
                    DetailView(item: item)
                }
            }
            .navigationTitle("Fruits")
        }
    }
}

struct DetailView: View {
    let item: Item
    var body: some View {
        Text("Detail: \(item.name)")
            .navigationTitle(item.name)
    }
}

// ForEach for custom layouts
ScrollView {
    LazyVStack {
        ForEach(items) { item in
            Text(item.name)
        }
    }
}

フォームとシート

Form は設定画面用にコントロールを自動スタイルします(グループ化、プラットフォーム適応)。一般的なコントロール:TextField、Toggle、Slider、Picker、Stepper。.sheet がモーダルを表示し、.fullScreenCover がフルスクリーン用です。バインディング($)がコントロールを状態に接続します。Form はプラットフォームに適応します(iOS グループ化リスト、macOS フォームレイアウト)。データ入力と設定には Form を、カスタムレイアウトには VStack を使用してください。

swift
struct SettingsView: View {
    @State private var name = ""
    @State private var notifications = true
    @State private var volume: Double = 0.5
    @State private var theme = "Light"
    @State private var showSheet = false

    var body: some View {
        Form {
            Section("Profile") {
                TextField("Name", text: $name)
                Toggle("Notifications", isOn: $notifications)
            }
            Section("Audio") {
                Slider(value: $volume, in: 0...1)
            }
            Section("Appearance") {
                Picker("Theme", selection: $theme) {
                    Text("Light").tag("Light")
                    Text("Dark").tag("Dark")
                }
            }
            Button("Show Modal") { showSheet = true }
        }
        .sheet(isPresented: $showSheet) {
            Text("Modal content")
        }
    }
}
15

クロージャ 深掘り

クロージャ構文のバリエーション

クロージャには多くの構文形式があります。完全形:{ (params) -> Type in body }。型推論で型を省略できます。省略引数($0、$1)が名前付きパラメータを置き換えます。後置クロージャ構文は最後の引数のときクロージャを () の外に移動します。複数後置クロージャ(Swift 5.3 以降)は追加クロージャに名前を付けます。読みやすい中で最も簡潔な形式を使用してください—map のような短いクロージャには省略引数が最適です。

swift
// Full closure
let add: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in
    return a + b
}

// Type inference
let add2: (Int, Int) -> Int = { a, b in a + b }

// Shorthand argument names ($0, $1...)
let add3: (Int, Int) -> Int = { $0 + $1 }

// Trailing closure syntax
let result = [1, 2, 3].map { $0 * 2 }  // [2, 4, 6]

// Multiple trailing closures (Swift 5.3+)
Button {
    print("action")  // action
} label: {
    Text("Click")    // label
}

// No return for Void closures
let log: (String) -> Void = { msg in print(msg) }

値のキャプチャ

クロージャは外側のスコープから変数をキャプチャします。デフォルトでは参照でキャプチャします(キャプチャされた変数への変更が見えます)。キャプチャリスト [foo] は値でキャプチャします(クロージャ作成時のスナップショット)。参照型(クラス)の場合、[weak self] または [unowned self] で保持サイクルを破ります。値型(構造体)を参照でキャプチャしても、クロージャがボックスを保持するため更新が見えます。キャプチャリストはパラメータリストの前に配置します。

swift
// Closures capture surrounding state
func makeIncrementer(amount: Int) -> () -> Int {
    var total = 0
    return {
        total += amount  // captures total and amount
        return total
    }
}

let inc = makeIncrementer(amount: 5)
inc()  // 5
inc()  // 10
// total persists between calls (captured by reference)

// Capture list controls how
class Foo { var x = 0 }
let foo = Foo()
let closure = { [foo] in  // captures foo by value (snapshot)
    print(foo.x)
}
foo.x = 100
closure()  // prints 0 (captured the old value)

// Without capture list, foo is captured by reference
let refClosure = { print(foo.x) }
foo.x = 200
refClosure()  // prints 200

Escaping と Autoclosure

@autoclosure は式を自動的にゼロ引数クロージャでラップし、呼び出し側が中括弧なしで式を直接記述できるようにします。これにより遅延評価が可能です—assert() はリリースビルドで条件をスキップするために使用します。@escaping と組み合わせて評価を遅延できます。&& と || 演算子は短絡評価のために @autoclosure を使用します。@autoclosure は控えめに使用してください—コードが遅延されることを隠し、読者を混乱させる可能性があります。

swift
// @autoclosure: wraps an expression in a closure automatically
func log(_ condition: @autoclosure () -> Bool, _ message: String) {
    if condition() {  // evaluated here, not at call
        print(message)
    }
}
log(2 > 1, "two is greater")  // no { } needed

// assert uses @autoclosure to avoid evaluating in release
assert(isValid, "Invalid state")  // isValid not checked in release

// @autoclosure + @escaping
func deferred(_ block: @autoclosure @escaping () -> Int) {
    DispatchQueue.main.async { print(block()) }
}
deferred(expensiveComputation())  // runs later, on main

// Short-circuit evaluation with @autoclosure
func &&(lhs: @autoclosure () -> Bool, rhs: @autoclosure () -> Bool) -> Bool {
    return lhs() ? rhs() : false  // rhs not evaluated if lhs is false
}

高階関数

Swift の高階関数(map、filter、reduce、flatMap、compactMap)はコレクション上の関数型プログラミングを可能にします。map は変換、filter は選択、reduce は集計、flatMap はフラット化、compactMap は nil を削除します。これらは新しいコレクションを返します(不変性)。チェーンで操作を合成します。大規模データセットには中間配列を避けるため .lazy を使用してください。これらが関数型 Swift の基盤です—データ変換時は命令型 for ループより優先してください。

swift
let nums = [1, 2, 3, 4, 5, 6]

// map: transform each element
let doubled = nums.map { $0 * 2 }  // [2, 4, 6, 8, 10, 12]

// filter: keep elements matching predicate
let evens = nums.filter { $0 % 2 == 0 }  // [2, 4, 6]

// reduce: combine all into one
let sum = nums.reduce(0, +)  // 21
let product = nums.reduce(1, *)  // 720

// flatMap: transform + flatten
let nested = [[1, 2], [3, 4]]
let flat = nested.flatMap { $0 }  // [1, 2, 3, 4]

// compactMap: transform + filter nils
let strings = ["1", "abc", "3"]
let ints = strings.compactMap { Int($0) }  // [1, 3]

// Chaining (lazy evaluation)
let result = nums
    .filter { $0 % 2 == 0 }
    .map { $0 * $0 }
    .reduce(0, +)  // 4 + 16 + 36 = 56

コンプリーションハンドラとしてのクロージャ

コンプリーションハンドラは async/await(Swift 5.5)以前の標準的な非同期パターンでした。これらは @escaping(後で実行)で、エラー処理に Result を使用することが多いです。欠点はネストされたコールバック(「ピラミッド・オブ・ドゥーム」)です。async/await はこれを線形で読みやすくします。新しいコードには async/await を使用すべきです。コンプリーションハンドラはデリゲート API と Objective-C 相互運用に残ります。withCheckedContinuation でコンプリーションハンドラをラップして async/await で使用できます。

swift
// Completion handler pattern (pre-async/await)
func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
    URLSession.shared.dataTask(with: url) { data, _, error in
        if let error = error {
            completion(.failure(error))
            return
        }
        guard let data = data,
              let user = try? JSONDecoder().decode(User.self, from: data) else {
            completion(.failure(DecodeError()))
            return
        }
        completion(.success(user))
    }.resume()
}

// Usage (pyramid of doom)
fetchUser(id: 42) { result in
    switch result {
    case .success(let user):
        fetchAvatar(user) { avatarResult in
            // nested...
        }
    case .failure(let error):
        print(error)
    }
}

// Modern: async/await replaces this
func fetchUserAsync(id: Int) async throws -> User {
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}
16

プロパティラッパー

プロパティラッパーの定義

プロパティラッパー(@propertyWrapper)は再利用可能なプロパティの振る舞いをカプセル化します。wrappedValue プロパティがユーザーがやり取りするものです。init が初期値と任意のカスタム引数を受け取ります。SwiftUI はプロパティラッパーを広範に使用します:@State、@Binding、@Published、@AppStorage。検証、キャッシュ、ログ、デフォルト値用に独自のものを定義してください。ラッパーはストレージを管理する構造体/クラスです。

swift
@propertyWrapper
struct Clamped<Value: Comparable> {
    var value: Value
    let range: ClosedRange<Value>

    var wrappedValue: Value {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }

    init(wrappedValue: Value, _ range: ClosedRange<Value>) {
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
        self.range = range
    }
}

// Usage
struct Slider {
    @Clamped(0...100) var progress: Int = 50
}
var s = Slider()
s.progress = 150  // clamped to 100
s.progress = -10  // clamped to 0
print(s.progress)  // 0

投影値

projectedValue($ 経由でアクセス)はプロパティへの第2のインターフェースです。SwiftUI はこれを多用します:@State の $ は子ビュー用の Binding を提供し、@Published の $ は Publisher を提供します。ラッパーがラップされた値を超えて何か(バインディング、パブリッシャー、ラッパー自体)を公開すべき場合に projectedValue を定義します。$ プレフィックス構文によりこれが人間工学的になります。すべてのラッパーが projectedValue を必要とするわけではありません—オプションです。

swift
@propertyWrapper
struct Observable<T> {
    var value: T
    var wrappedValue: T {
        get { value }
        set { value = newValue; onChange() }
    }
    var onChange: () -> Void = {}

    // projectedValue: accessed via $ (like @State's binding)
    var projectedValue: Observable<T> { self }
}

struct View {
    @Observable var count = 0
    // count: the wrapped value (Int)
    // $count: the projected value (Observable<Int>)
}

// SwiftUI's @State projects a Binding
struct Counter: View {
    @State var value = 0
    var body: some View {
        Stepper("Value: \(value)", value: $value)  // Binding<Int>
    }
}

組み込みプロパティラッパー

SwiftUI は多くのプロパティラッパーを提供します:@State(ローカル状態)、@Binding(親状態)、@ObservedObject/@StateObject(外部モデル)、@EnvironmentObject(注入されたモデル)、@AppStorage(UserDefaults)、@SceneStorage(シーン復元)、@FocusState(キーボードフォーカス)、@ScaledMetric(Dynamic Type)、@Namespace(アニメーション)。それぞれがストレージとライフサイクルを管理します。Combine の @Published が UI 更新をトリガーします。いつどれを使うかを知ることが SwiftUI の鍵です。

swift
import SwiftUI
import Combine

struct ContentView: View {
    @State var count = 0  // local view state

    @Binding var externalValue: Int  // parent-owned

    @EnvironmentObject var model: AppModel  // injected

    @AppStorage("username") var username = ""  // UserDefaults

    @SceneStorage("draft") var draft = ""  // per-scene storage

    @FocusState var isFocused: Bool  // focus management

    @ScaledMetric var size = 16  // Dynamic Type scaling

    @Namespace var animationNamespace  // matched geometry

    // Combine
    @Published var data = [Item]()  // in ObservableObject
}

// @AppStorage auto-syncs with UserDefaults
// @SceneStorage restores state per scene (iPad multitasking)

検証用カスタムラッパー

プロパティラッパーは横断的関心事に輝きます。NonEmpty は set で検証します。Cached は遅延計算しメモ化します。他のアイデア:Logged(変更をログ)、Trimmed(空白をトリム)、Formatted(get でフォーマット)、Clamped(範囲制限)、UserDefaults-backed。ラッパーはボイラープレートを削減します—ロジックを一度書き @ で適用します。それらは合成できます:@Logged @Clamped(0...100) var value。ラッパーは1つの関心事に集中させてください。

swift
@propertyWrapper
struct NonEmpty {
    private var value: String = ""
    var wrappedValue: String {
        get { value }
        set {
            guard !newValue.isEmpty else {
                print("Warning: cannot set to empty")
                return
            }
            value = newValue
        }
    }
    init(wrappedValue: String) {
        self.wrappedValue = wrappedValue
    }
}

@propertyWrapper
struct Cached<T> {
    private var cached: T?
    private var generator: () -> T
    var wrappedValue: T {
        mutating get {
            if cached == nil { cached = generator() }
            return cached!
        }
    }
    init(wrappedValue: @autoclosure @escaping () -> T) {
        self.generator = wrappedValue
    }
}

struct Config {
    @NonEmpty var name: String = "default"
    @Cached var expensiveValue: Int = computeExpensive()
}

プロトコルとジェネリクスのプロパティラッパー

プロパティラッパーはジェネリックでプロトコルで使用できます。ただし制限があります:ラップされたプロパティを持つ構造体はメンバーワイズ初期化子を取得しません(init を手動で書く必要があります)、コピーは注意が必要です(ラッパーは状態を含めてコピーされます)。クラスではこれは問題になりません。制限にもかかわらず、ラッパーは宣言的で再利用可能なプロパティの振る舞いに強力です。SwiftUI の設計はこれらに大きく依存しています。

swift
// Apply to protocol requirements
protocol Configurable {
    @NonEmpty var name: String { get set }
}

// Generic property wrapper
@propertyWrapper
struct Validated<T> {
    private var value: T?
    let validator: (T) -> Bool
    var wrappedValue: T? {
        get { value }
        set {
            if let v = newValue, validator(v) { value = v }
        }
    }
    init(wrappedValue: T?, validator: @escaping (T) -> Bool) {
        self.validator = validator
        self.wrappedValue = wrappedValue
    }
}

struct Form {
    @Validated(validator: { $0.count >= 8 }) var password: String? = nil
}

// Limitation: property wrappers in structs can't be
// initialized from another instance easily (copy issues).
// They work best as stored properties, not computed.
17

Combine フレームワーク

パブリッシャーとサブスクライバー

Combine は Apple のリアクティブフレームワークです(RxSwift のようなもの)。パブリッシャーが値を発行し、サブスクライバーが受け取ります。sink がクロージャを持つサブスクライバーを作成します。assign が出力をプロパティにバインドします。演算子(map、filter、reduce)がパブリッシャーを宣言的に変換します。Combine は宣言的です—パイプラインを記述し、値が流れます。非同期操作、UI バインディング、イベント処理に使用されます。モダンな Swift は async/await を優先しますが、複雑なパイプラインには Combine が残ります。

swift
import Combine

// Publisher: emits values over time
let publisher = [1, 2, 3].publisher

// Subscriber: receives values
publisher.sink { value in
    print(value)  // 1, 2, 3
}

// With completion
publisher.sink(receiveCompletion: { completion in
    print("done: \(completion)")
}, receiveValue: { value in
    print(value)
})

// Assign to a property
class ViewModel {
    var data: String = ""
}
let vm = ViewModel()
Just("hello").assign(to: &vm.data)

// Operators transform publishers
[1, 2, 3, 4].publisher
    .map { $0 * 2 }
    .filter { $0 > 4 }
    .sink { print($0) }  // 6, 8

@Published と ObservableObject

@Published はプロパティをラップし、変更されるたびに新しい値をサブスクライバーに発行します。$ プレフィックスがパブリッシャーにアクセスします。これが Combine と SwiftUI を橋渡しします—ObservableObject の @Published がビュー更新をトリガーします。サブスクリプションは AnyCancellable が解放されるとキャンセルされるため、保存してください(例:Set に)。デバウンス、スロットリング、複数の非同期ソースの結合に Combine を使用してください—async/await があまり優雅に扱わないものです。

swift
class UserStore: ObservableObject {
    @Published var name: String = ""  // publishes on change
    @Published var age: Int = 0

    // $name is a Publisher<String, Never>
    func demo() {
        $name.sink { print("name changed to \($0)") }
        name = "Alice"  // prints "name changed to Alice"
    }
}

// Combine with SwiftUI
struct ProfileView: View {
    @ObservedObject var store: UserStore
    var body: some View {
        Text(store.name)  // updates when name changes
    }
}

// Cancellable: store subscriptions to keep them alive
class Service {
    var cancellables = Set<AnyCancellable>()
    let store = UserStore()

    init {
        store.$name
            .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
            .sink { name in saveToServer(name) }
            .store(in: &cancellables)  // keep alive
    }
}

演算子

Combine 演算子はパブリッシャーを変換、フィルタ、結合、時間制御します。map/scan が変換、filter/セレクタが選択、merge/zip/combineLatest が複数ストリームを結合、debounce/throttle がタイミングを制御します。演算子は新しいパブリッシャーを返します(不変性、チェーン)。debounce は静寂期間を待ち(検索入力)、throttle はレートを制限します(ボタンタップ)。collect がすべての値を配列に集めます。これらが宣言的リアクティブパイプラインを可能にします。

swift
let publisher = [1, 2, 3, 4, 5].publisher

// Transforming
publisher.map { $0 * 2 }  // [2, 4, 6, 8, 10]
publisher.scan(0, +)  // [1, 3, 6, 10, 15] (running sum)

// Filtering
publisher.filter { $0 % 2 == 0 }  // [2, 4]
publisher.removeDuplicates()  // consecutive dupes
publisher.first()  // just 1

// Combining
let p1 = [1, 2].publisher
let p2 = [3, 4].publisher
p1.merge(with: p2)  // interleaved: 1, 2, 3, 4 (or any order)
p1.zip(p2)  // pairs: (1,3), (2,4)
p1.combineLatest(p2)  // latest of each

// Timing
publisher.delay(for: 1, scheduler: RunLoop.main)
publisher.throttle(for: 1, scheduler: .main, latest: true)
publisher.debounce(for: 0.5, scheduler: .main)
publisher.collect()  // gather all into [1,2,3,4,5]

サブジェクト(手動発行)

サブジェクトは手動で値を送信できる可変パブリッシャーです—命令型とリアクティブコードを橋渡しします。PassthroughSubject は保存せずにブロードキャストします(イベントストリーム)。CurrentValueSubject は最新値を保存します(状態)。CurrentValueSubject の新しいサブスクライバーは即座に現在値を取得します。サブジェクトを使用してデリゲート、通知、UI イベントを Combine パイプラインにラップします。@Published は本質的に ObservableObject と統合された CurrentValueSubject です。

swift
import Combine

// PassthroughSubject: broadcasts to subscribers, no current value
let subject = PassthroughSubject<Int, Never>()
subject.sink { print($0) }  // subscriber 1
subject.send(1)  // prints 1
subject.send(2)  // prints 2

// CurrentValueSubject: holds current value, new subs get it
let current = CurrentValueSubject<Int, Never>(0)
current.sink { print($0) }  // prints 0 (current)
current.send(1)  // prints 1
current.sink { print($0) }  // prints 1 (current)
current.value  // 1

// Use cases:
// - PassthroughSubject: events (button taps, notifications)
// - CurrentValueSubject: state (like @Published but more control)
// - Bridge imperative code to Combine

// Finish a subject
subject.send(completion: .finished)

エラー処理

Combine パブリッシャーには Failure 型があります(失敗しない場合は Never)。catch がエラーをフォールバックパブリッシャーで置き換えます。retry が失敗時に再サブスクライブします(不安定なネットワークに良い)。mapError がエラー型を変換します。assertNoFailure はエラー発生時にクラッシュします(確信がある場合に使用)。Failure == Never のパブリッシャーはどこでも使用できます。エラーのあるものは処理が必要です。これによりリアクティブパイプラインでエラー処理が明示的で合成可能になります。

swift
enum APIError: Error { case network, parse }

// Publishers can fail with an error
let publisher = Fail<Int, APIError>(error: .network)

// catch: replace error with fallback publisher
publisher
    .catch { _ in Just(0) }  // fallback to 0
    .sink { print($0) }  // 0

// retry: retry on failure
URLSession.shared.dataTaskPublisher(for: url)
    .retry(3)  // retry up to 3 times
    .sink(receiveCompletion: { _ in }, receiveValue: { _ in })

// mapError: transform error type
publisher
    .mapError { _ in NSError(domain: "x", code: 1) }

// assertNoFailure: crash if error (when you're sure it won't)
Just(1).assertNoFailure().sink { print($0) }

// Result publisher
let resultPublisher = [1, 2, 3].publisher.setFailureType(to: Error.self)
18

アクセス制御とコード編成

アクセスレベル

Swift には5つのアクセスレベルがあります。private(最小スコープ、宣言内)、fileprivate(ソースファイル内)、internal(モジュール内、デフォルト)、public(インポートする誰でも)、open(public + サブクラス化/オーバーライド可能、クラスのみ)。Swift 5.9 は SPM モジュール用に package を追加します。制限的に始め、必要に応じて広げてください。ライブラリ API には public、アプリコードには internal、実装の詳細には private を使用してください。open はサブクラス化を想定して設計されたフレームワーククラス用です。

swift
// public: accessible from any module that imports this
public class PublicAPI {
    public func connect() {}
}

// internal (default): accessible within the module
class InternalClass {
    func helper() {}  // internal by default
}

// fileprivate: accessible within the source file
fileprivate func fileHelper() {}

// private: accessible within the enclosing declaration
class Counter {
    private var count = 0  // only Counter can access
    fileprivate func reset() { count = 0 }  // same file
}

// Swift 5.9: package (for Swift Package Manager)
package struct PackageType {
    package var value: Int
}

// Guidelines:
// - Start restrictive (private), open up as needed
// - public for framework API, internal for implementation
// - private for encapsulation within a type

拡張と編成

拡張により型の機能をファイル間で分割し(String+Validation.swift、String+Parsing.swift)、遡的に準拠を追加できます。これによりファイルが焦点を絞んで管理しやすくなります。プロトコル準拠はメインの型定義とは別の拡張/ファイルに配置できます。条件付き準拠(where Element: ...)が条件付きで準拠を追加します。拡張で編成してください:コア型を最小限に保ち、焦点を絞った拡張ファイルに機能を追加します。

swift
// Split a type across files with extensions
// String+Validation.swift
extension String {
    var isValidEmail: Bool {
        contains("@") && contains(".")
    }
}

// String+Parsing.swift
extension String {
    func toURL() -> URL? { URL(string: self) }
}

// Conform to protocols in extensions
extension String: Comparable {  // already conforms, just showing
    // protocol methods
}

// Conditional conformance
extension Array: MyProtocol where Element: MyProtocol {}

// Protocol conformance in a separate file
// (keeps the main type definition clean)
struct User { let id: Int; let name: String }

extension User: Codable {}  // synthesized
extension User: Equatable {}  // synthesized
extension User: CustomStringConvertible {
    var description: String { "User(\(id), \(name))" }
}

モジュールとパッケージ

Swift Package Manager(SPM)は Package.swift でモジュールを定義します。各ターゲットがモジュールです—internal アクセスはターゲット内、public はモジュール間。依存関係は他のパッケージ(git URL)です。プロダクトはパッケージが公開するものです。SPM は Swift コードを管理するモダンな方法です(CocoaPods/Carthage を置き換え)。Xcode は SPM をネイティブに統合します。構造:コードは Sources/MyLibrary/、テストは Tests/MyLibraryTests/。モジュールで関心を分離しアクセスを制御してください。

swift
// Package.swift (Swift Package Manager)
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "MyLibrary",
    products: [
        .library(name: "MyLibrary", targets: ["MyLibrary"]),
    ],
    dependencies: [
        .package(url: "https://github.com/.../Logging", from: "1.0.0"),
    ],
    targets: [
        .target(name: "MyLibrary", dependencies: ["Logging"]),
        .testTarget(name: "MyLibraryTests", dependencies: ["MyLibrary"]),
    ]
)

// Importing modules
import Foundation
import MyLibrary  // your module
import Logging    // dependency

// Each target is a module
// internal access is within a target/module
// public access crosses module boundaries

初期化子と指定/コンビニエンス

クラスには指定イニシャライザ(完全に初期化、super の指定を呼ぶ必要あり)とコンビニエンスイニシャライザ(同じクラスの別の init に委譲)があります。この2層システムが完全な初期化を保証します。構造体はメンバーワイズイニシャライザを無料で取得します。失敗可能イニシャライザ(init?)は失敗時に nil を返します。必須イニシャライザ(required init)はサブクラスが実装する必要があります。ほとんどのコードでは、クラスより構造体を優先してください(シンプルな初期化)。

swift
class Person {
    let name: String
    let age: Int

    // Designated initializer: fully initializes
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

class Employee: Person {
    let employeeId: String

    // Designated init must call super's designated init
    init(name: String, age: Int, employeeId: String) {
        self.employeeId = employeeId
        super.init(name: name, age: age)  // must call super
    }

    // Convenience init must call another init (same class)
    convenience init(name: String) {
        self.init(name: name, age: 0, employeeId: "TEMP")
    }
}

// Structs: memberwise initializer (free)
struct Point { let x, y: Int }
let p = Point(x: 1, y: 2)  // synthesized

// Failable initializers
struct Config {
    let port: Int
    init?(port: Int) {
        guard port > 0 else { return nil }
        self.port = port
    }
}

オプショナル 深掘り

オプショナルは Swift の null 安全メカニズムです—.some(value) または .none(nil)のいずれかの列挙型です。値にアクセスするにはアンラップする必要があります。オプショナルチェーン(?.)が安全にナビゲートし、nil 合体(??)がデフォルトを提供し、if let/guard let が安全にアンラップします。確信がない限り !(強制アンラップ)を避けてください—nil でクラッシュします。オプショナルにより不在を明示的に処理することを強制し、他の言語で一般的な null 参照例外を排除します。

swift
// Optional: enum with .some(Value) and .none
let maybeInt: Int? = 42
let nilInt: Int? = nil

// Optional chaining (short-circuits on nil)
let name: String? = user?.profile?.name  // nil if any link is nil

// Nil-coalescing (default value)
let value = maybeInt ?? 0  // 42
let value2 = nilInt ?? 0   // 0

// Forced unwrapping (crashes if nil)
let forced = maybeInt!  // 42
// let crash = nilInt!   // RUNTIME CRASH

// Optional binding
if let actual = maybeInt {
    print(actual)  // 42, actual is Int (not optional)
}

// Guard for early exit
func process(_ data: Data?) {
    guard let data = data else { return }
    // data is non-optional here
}

// Optional try
let result = try? parse(json)  // nil on error
let result2 = try! parse(json) // crashes on error

// Multiple binding
if let a = optA, let b = optB, a < b { /* ... */ }
19

メモリ管理(ARC/weak)

ARC の基礎

自動参照カウント(ARC)が強参照を追跡します。カウントがゼロになるとオブジェクトは解放されます。ARC は決定的です(ガベージコレクションとは異なります):最後の参照が解放されると deinit が即座に実行されます。ほとんどの場合、ARC はそのまま動作します。問題は参照サイクルで発生します:2つのオブジェクトが互いに強参照し、解放を防ぎます。weak または unowned でサイクルを破ってください。

swift
class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }
    deinit {
        print("\(name) is being deinitialized")
    }
}

var reference1: Person?
var reference2: Person?

reference1 = Person(name: "Alice")
// Prints: Alice is being initialized

reference2 = reference1  // Strong reference count: 2

reference1 = nil  // Count: 1
reference2 = nil  // Count: 0, deinit runs
// Prints: Alice is being deinitialized

弱参照

weak 参照は参照先のオブジェクトを存続させません。オブジェクトが解放されると、weak 参照は自動的に nil になります。weak は optional である必要があります(var tenant: Person?)。参照先のオブジェクトのライフタイムが短い場合に weak を使用してください(テナントがアパートを出る可能性)。古典的なユースケースはデリゲートパターンです:サイクルを避けるためデリゲートは weak です。weak にはわずかなオーバーヘッドがあります(nil 設定のためにランタイムに登録)。

swift
class Person {
    var name: String
    var apartment: Apartment?  // Strong (person owns apartment)
    init(name: String) { self.name = name }
    deinit { print("\(name) deinit") }
}

class Apartment {
    var unit: String
    weak var tenant: Person?  // Weak (breaks cycle)
    init(unit: String) { self.unit = unit }
    deinit { print("Apartment \(unit) deinit") }
}

var alice: Person? = Person(name: "Alice")
var apt: Apartment? = Apartment(unit: "4A")

alice!.apartment = apt
apt!.tenant = alice  // Weak reference, no cycle

alice = nil  // Alice deinit (apartment's tenant becomes nil)
apt = nil    // Apartment deinit

// weak must be optional (becomes nil when deallocated)

非所有参照

unowned 参照は weak と同様にオブジェクトを存続させません。weak とは異なり、unowned は非オプショナルで nil になりません。解放後に unowned 参照にアクセスするとクラッシュします。参照先のオブジェクトが参照より長く存続することを保証できる場合(例:クレジットカードは顧客なしでは存在できない)に unowned を使用してください。unowned は weak よりオーバーヘッドが少ないです。選択:不確実なライフタイムには weak、保証されたライフタイムには unowned。

swift
class Customer {
    let name: String
    var card: CreditCard?  // Strong
    init(name: String) { self.name = name }
    deinit { print("\(name) deinit") }
}

class CreditCard {
    let number: UInt
    unowned let customer: Customer  // Non-optional, no cycle
    init(number: UInt, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card \(number) deinit") }
}

var john: Customer? = Customer(name: "John")
john!.card = CreditCard(number: 1234, customer: john!)

john = nil
// Customer deinit, then CreditCard deinit

// unowned: non-optional, assumed to always have a value
// CRASH if accessed after deallocation
// Use when the referenced object has same or longer lifetime

クロージャとキャプチャリスト

クロージャはデフォルトで参照を強くキャプチャし、クロージャが self に格納される場合にサイクルを引き起こします。キャプチャリスト([weak self] または [unowned self])がサイクルを破ります。weak self はオプショナル処理を要求します(guard let self = self)。self 全体をキャプチャするのを避けるため特定の値をキャプチャします(let name = self.name)。関数に渡される(格納されない)クロージャはサイクルを引き起こしません。self を参照する格納クロージャには常にキャプチャリストを使用してください。

swift
class ViewController {
    var name = "MyVC"
    var onComplete: (() -> Void)?

    // BAD: Strong reference cycle
    func setupBad() {
        onComplete = {
            print("\(self.name) completed")  // Captures self strongly
        }
    }

    // GOOD: Capture list breaks cycle
    func setupGood() {
        onComplete = { [weak self] in
            guard let self = self else { return }
            print("\(self.name) completed")
        }
    }

    // unowned for guaranteed lifetime
    func setupUnowned() {
        onComplete = { [unowned self] in
            print("\(self.name) completed")
        }
    }

    // Capture specific values
    func setupCapture() {
        let name = self.name  // Capture value, not self
        onComplete = {
            print("\(name) completed")
        }
    }

    deinit { print("VC deinit") }
}

リークの検出と修正

一般的なリークソース:強参照で格納されたデリゲート(weak を使用)、ターゲットを保持する Timer(deinit で invalidate)、NotificationCenter オブザーバー(deinit で削除)。Xcode Memory Graph Debugger がオブジェクトグラフを視覚化しリークをハイライトします。MallocStackLogging で実行して割り当てトレースを取得します。参照を nil に設定し deinit が実行されることを確認してテストします。Instruments(Leaks ツール)が実行時にリークを検出します。常にセットアップとティアダウンをペアにしてください。

swift
// Xcode memory graph debugger: visualize reference cycles
// Product > Debug > View Memory Graph Hierarchy

// Common leak patterns:
// 1. Delegate stored as strong
class View {
    weak var delegate: ViewDelegate?  // MUST be weak
}

// 2. Timer retains target
class TimerHolder {
    var timer: Timer?
    func start() {
        // BAD: timer retains self via #selector
        timer = Timer.scheduledTimer(timeInterval: 1,
            target: self, selector: #selector(fire),
            userInfo: nil, repeats: true)
    }
    @objc func fire() {}
    deinit { timer?.invalidate() }  // MUST invalidate
}

// 3. NotificationCenter observer
class Observer {
    init() {
        NotificationCenter.default.addObserver(
            self, selector: #selector(handle),
            name: .someNotification, object: nil)
    }
    @objc func handle() {}
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

// Use Xcode's Debug Memory Graph to find leaks
20

アクターと async/await

async/await の基礎

async/await(Swift 5.5 以降)は非同期コードを同期コードのように見せます。async が中断できる関数をマークし、await が中断ポイントをマークします。Task が同期から非同期コンテキストへの橋渡しをします。async let が並行タスクを開始し、await が結果を収集します。並列実行には async let を、逐次には通常の await を使用してください。エラーは throws/try で伝播します。コンパイラが中断ポイントで await を強制します。async/await は多くのユースケースでコンプリーションハンドラと Combine を置き換えます。

swift
// Async function
func fetchUser(id: Int) async throws -> User {
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// Calling async functions
func loadProfile() async {
    do {
        let user = try await fetchUser(id: 1)
        print("Loaded: \(user.name)")
    } catch {
        print("Error: \(error)")
    }
}

// Task: bridge async to sync context
Task {
    await loadProfile()
}

// Concurrent execution
func loadMultiple() async {
    async let user1 = fetchUser(id: 1)
    async let user2 = fetchUser(id: 2)
    async let user3 = fetchUser(id: 3)

    let users = try await [user1, user2, user3]
    print("Loaded \(users.count) users")
}

// Sequenced vs concurrent
let sequential = try await fetchUser(id: 1)  // Waits
let concurrent = try await fetchUser(id: 2)  // Then waits again

アクター

アクター(Swift 5.5 以降)はアクセスを直列化する参照型で、データ競合を防ぎます。一度に1つのメソッドのみがアクターで実行されます—手動ロックが不要です。アクターメソッドは await で呼び出す必要があります(中断する可能性があります)。プロパティは隔離されており外部から直接アクセスできません。アクターは並行コードの共有された可変状態の安全なデフォルトです。ほとんどの並行状態にロックを持つクラスの代わりにアクターを使用してください。

swift
// Actor: reference type with serialized access
actor BankAccount {
    private var balance: Decimal

    init(balance: Decimal) {
        self.balance = balance
    }

    func deposit(_ amount: Decimal) {
        balance += amount
    }

    func withdraw(_ amount: Decimal) -> Bool {
        guard balance >= amount else { return false }
        balance -= amount
        return true
    }

    func getBalance() -> Decimal { balance }
}

// Usage (must be async)
let account = BankAccount(balance: 100)
Task {
    await account.deposit(50)
    let balance = await account.getBalance()
    print("Balance: \(balance)")  // 150
}

// Actors prevent data races automatically
// Only one method runs at a time per actor instance
// No need for locks or queues

アクター隔離

アクター隔離が状態を保護します。nonisolated メンバーは await なしでアクセスできます(純粋な値や隔離された状態に触れないメソッド用)。@MainActor はメインスレッド隔離用のグローバルアクターです(UI 更新)。@globalActor がカスタムグローバルアクターを作成します。アクター境界を越えるには await が必要です。コンパイラがコンパイル時に隔離を検証し、データ競合を防ぎます。ViewModel と UI コードには @MainActor を、ドメイン固有の隔離にはカスタムアクターを使用してください。

swift
actor Counter {
    private var count = 0

    // Isolated method (default)
    func increment() { count += 1 }

    // Nonisolated: can be called without await
    nonisolated var description: String { "Counter" }

    // nonisolated function
    nonisolated func id() -> String { "counter-\(ObjectIdentifier(self))" }
}

// Global actor: @MainActor runs on main thread
@MainActor
class ViewModel: ObservableObject {
    @Published var data: [Item] = []

    func loadData() async {
        // Always runs on main thread
        data = try await fetchItems()
    }
}

// Mark specific functions
class Service {
    @MainActor
    func updateUI(_ result: Result) {
        // UI updates on main thread
    }
}

// globalActor lets you apply to types, functions, properties
@globalActor
actor MyActor {
    static let shared = MyActor()
}

構造化並行性

タスクグループが動的並列計算を可能にします。addTask が子タスクをスポーンし、for await group が完了した結果を収集します。タスクグループは構造化並行性を強制します:すべての子が親が続行する前に完了します。キャンセルは親から子に自動的に伝播します。長時間実行タスクで Task.isCancelled をチェックしてください。withThrowingTaskGroup がエラーを伝播し、スロー時に兄弟をキャンセルします。ファンアウト/ファンインパターンにタスクグループを使用してください。

swift
// TaskGroup: dynamic parallel computation
func fetchAllUsers(ids: [Int]) async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        for id in ids {
            group.addTask { try await fetchUser(id: id) }
        }

        var users: [User] = []
        for try await user in group {
            users.append(user)
        }
        return users
    }
}

// Task tree: parent-child relationship
func process() async {
    await withTaskGroup(of: Void.self) { group in
        for i in 1...5 {
            group.addTask {
                await processItem(i)
            }
        }
        // All tasks complete before group returns
    }
    print("All done")
}

// Cancellation
func longRunning() async {
    await withTaskGroup(of: Void.self) { group in
        group.addTask {
            for i in 1...100 {
                if Task.isCancelled { return }
                await doWork(i)
            }
        }
    }
}

// Cancel from outside
let task = Task { await longRunning() }
task.cancel()

AsyncSequence とストリーム

AsyncSequence は Sequence の非同期版で、for await ループを可能にします。AsyncStream がコールバックベースまたはデリゲートベースの API を async/await に橋渡しします。yield が値を発行し、finish が終了します。onTermination がリソースをクリーンアップします。AsyncThrowingStream がエラーをサポートします。ページネーションされた API、リアルタイムデータ、時間とともに値のストリームに AsyncSequence を使用してください。標準ライブラリは URL に .lines を提供し行ごとのファイル読み取りをします。AsyncSequence はタスクキャンセルと統合します。

swift
// AsyncSequence: iterate asynchronously
struct Counter: AsyncSequence {
    struct AsyncIterator: AsyncIteratorProtocol {
        var current: Int
        let max: Int
        mutating func next() async -> Int? {
            guard current <= max else { return nil }
            defer { current += 1 }
            try? await Task.sleep(nanoseconds: 100_000_000)
            return current
        }
    }

    let start: Int
    let max: Int

    func makeAsyncIterator() -> AsyncIterator {
        AsyncIterator(current: start, max: max)
    }
}

// Usage
for await num in Counter(start: 1, max: 5) {
    print(num)  // 1 2 3 4 5 (with delays)
}

// AsyncStream: bridge callbacks to async
func urlLines(_ url: URL) -> AsyncThrowingStream<String, Error> {
    AsyncThrowingStream { continuation in
        let task = Task {
            do {
                for try await line in url.lines {
                    continuation.yield(line)
                }
                continuation.finish()
            } catch {
                continuation.finish(throwing: error)
            }
        }
        continuation.onTermination = { _ in task.cancel() }
    }
}
21

テスト

XCTest の基礎

XCTest は標準テストフレームワークです。XCTestCase が関連テストをグループ化します。setUp/tearDown が各テストの前後に実行されます。アサーション:XCTAssertEqual、XCTAssertNil、XCTAssertThrowsError、XCTAssertTrue。@testable import が internal シンボルにアクセスします。テストはデフォルトでメインスレッドで実行されます。オプショナルには XCTAssertNotNil を使用してください。Cmd+U でテストを実行します。Xcode がテスト結果をインライン表示します。高速なフィードバックのためにコードと並行してテストを書いてください。

swift
import XCTest
@testable import MyApp

class UserTests: XCTestCase {
    var user: User!

    override func setUp() {
        super.setUp()
        user = User(name: "Alice", age: 30)
    }

    override func tearDown() {
        user = nil
        super.tearDown()
    }

    func testName() {
        XCTAssertEqual(user.name, "Alice")
    }

    func testAgeAfterBirthday() {
        user.haveBirthday()
        XCTAssertEqual(user.age, 31)
    }

    func testValidation() {
        XCTAssertThrowsError(try User(name: "", age: -1)) { error in
            guard let error = error as? UserError else {
                XCTFail("Wrong error type")
                return
            }
            XCTAssertEqual(error, .invalidName)
        }
    }

    func testOptional() {
        XCTAssertNotNil(user.email)
        XCTAssertNil(user.address)
    }
}

// Run: Cmd+U in Xcode

非同期テスト

Swift 5.5 以降は非同期テストメソッドを直接サポートします(async throws func test...)。テスト内で try await を使用します。古いコードには XCTestExpectation を使用します:期待を作成、コールバックで fulfill()、wait(for:timeout:)。ハングをキャッチするため妥当なタイムアウトを設定します。非同期シーケンスのテストには for await ループを使用します。URLProtocol または依存性注入でネットワーク呼び出しをモックします。成功と失敗の両方のパスをテストします。非同期テストはデフォルトで並行実行—順序が重要な場合は .serialized トレイトを使用します。

swift
class APITests: XCTestCase {
    func testFetchUser() async throws {
        let user = try await fetchUser(id: 1)
        XCTAssertEqual(user.name, "Alice")
    }

    func testFetchFailure() async {
        do {
            _ = try await fetchUser(id: -1)
            XCTFail("Should have thrown")
        } catch {
            // Expected
        }
    }

    // Old style with expectations (still works)
    func testAsyncWithExpectation() {
        let expectation = XCTestExpectation(description: "Fetch completes")

        fetchUserAsync { result in
            switch result {
            case .success(let user):
                XCTAssertEqual(user.name, "Alice")
            case .failure:
                XCTFail("Should succeed")
            }
            expectation.fulfill()
        }

        wait(for: [expectation], timeout: 5)
    }
}

モックとスタブ

依存性注入がテストを可能にします:依存関係のプロトコルを定義し、テストでモックを注入します。MockNetworkService が所定の結果を返します。mock.fetchUserResult を設定して成功と失敗のケースをテストします。このパターンによりテストがネットワークから切り離され、高速で決定的になります。複雑なモックには Cuckoo や Mockingbird のようなライブラリを使用します。実装の詳細ではなく型の public API をテストしてください。モックはシンプルで焦点を絞ったものにすべきです。

swift
// Protocol for dependency injection
protocol NetworkService {
    func fetchUser(id: Int) async throws -> User
}

// Production implementation
class APIService: NetworkService {
    func fetchUser(id: Int) async throws -> User {
        // Real network call
    }
}

// Mock for testing
class MockNetworkService: NetworkService {
    var fetchUserResult: Result<User, Error>?

    func fetchUser(id: Int) async throws -> User {
        switch fetchUserResult {
        case .success(let user): return user
        case .failure(let error): throw error
        case .none: throw URLError(.badURL)
        }
    }
}

class ViewModelTests: XCTestCase {
    func testLoadUser() async throws {
        let mock = MockNetworkService()
        mock.fetchUserResult = .success(User(name: "Alice", age: 30))

        let vm = ViewModel(service: mock)
        await vm.load()

        XCTAssertEqual(vm.userName, "Alice")
    }
}

UI テスト

UI テストはアクセシビリティ識別子経由でユーザー操作を自動化します。XCUIApplication がアプリを起動します。アクセシビリティ識別子で要素を見つけます(コードで .accessibilityIdentifier で設定)。アクション:tap、typeText、swipeLeft/Right。アサーション:waitForExistence、exists、hittable。UI テストはユニットテストより遅いですが統合バグをキャッチします。SwiftUI で .accessibilityIdentifier("Email") でアクセシビリティ識別子を設定します。カバレッジのために複数のデバイス/シミュレータで UI テストを実行してください。

swift
import XCTest

class AppUITests: XCTestCase {
    override func setUpWithError() throws {
        continueAfterFailure = false
        let app = XCUIApplication()
        app.launch()
    }

    func testLoginFlow() {
        let app = XCUIApplication()

        let emailField = app.textFields["Email"]
        let passwordField = app.secureTextFields["Password"]
        let loginButton = app.buttons["Log In"]

        emailField.tap()
        emailField.typeText("[email protected]")

        passwordField.tap()
        passwordField.typeText("password123")

        loginButton.tap()

        // Verify navigation
        XCTAssertTrue(app.staticTexts["Welcome, Alice!"].waitForExistence(timeout: 5))
    }

    func testSwipeAndTap() {
        let cell = app.cells["Item 5"]
        cell.swipeLeft()
        app.buttons["Delete"].tap()
        XCTAssertFalse(cell.exists)
    }
}

パフォーマンスとコードカバレッジ

measure がコードパフォーマンスをベンチマークし、ベースラインと比較します。Xcode でベースラインを設定し、テストは回帰が閾値を超えると失敗します。measureMetrics が特定のメトリクス(ピークメモリ、CPU)を追跡します。テストされていないコードパスを確認するため、スキームでコードカバレッジを有効にしてください。ビジネスロジックで80%以上のカバレッジを目指してください。スナップショットテスト(swift-snapshot-testing)は視覚的回帰のために UI をキャプチャします。より深い分析には Instruments(Time Profiler、Allocations)でプロファイルしてください。パフォーマンステストは回帰を早期にキャッチします。

swift
class PerformanceTests: XCTestCase {
    func testSortingPerformance() {
        let data = (1...10000).shuffled()

        measure {
            _ = data.sorted()
        }
    }

    func testMemoryUsage() {
        measureMetrics([XCTPerformanceMetric.peakMemory]) {
            _ = (0..<10000).map { _ in [Int](repeating: 0, count: 100) }
        }
    }

    // Baseline comparison
    func testWithBaselines() {
        let options = XCTMeasureOptions()
        options.defaultOptions.measurementUnits = .seconds

        measure(options) {
            // Code to benchmark
        }
    }
}

// Code coverage: enable in Edit Scheme > Test > Options
// View coverage: Cmd+Shift+Y (Report Navigator) > Coverage

// Snapshot testing (third-party)
// func testAppearance() {
//     assertSnapshot(of: view, as: .image)
// }
22

Swift 並行性

async/await

async/await(Swift 5.5 以降)は非同期コードを簡素化します。async が中断できる関数をマークし、await が中断ポイントをマークします。Task が非同期コンテキストを作成します。コンプリーションハンドラよりはるかにクリーンです。エラーは try で伝播します。

swift
func fetchData() async throws -> String {
    let (data, _) = try await URLSession.shared.data(from: url)
    return String(data: data, encoding: .utf8) ?? ""
}
// Call from async context
Task {
    let result = try await fetchData()
    print(result)
}

アクター

actor(Swift 5.5 以降)は自動的な相互排他を持つ参照型です。一度に1つのタスクのみがその状態にアクセスします。外部から呼び出されるメソッドは暗黙的に async になります。共有された可変状態に対するロックやディスパッチキューを置き換えます。設計によりスレッドセーフです。

swift
actor Counter {
    private var count = 0
    func increment() {
        count += 1
    }
    func getValue() -> Int {
        count
    }
}
// Usage
Task {
    let counter = Counter()
    await counter.increment()
    let value = await counter.getValue()
}

Async Sequence

AsyncSequence は Sequence の非同期版です。for await で非同期に反復します。ストリーミングデータ(ネットワーク、ファイル行)に有用です。AsyncIteratorProtocol を実装してカスタム非同期シーケンスを作成します。コンパイラが中断とキャンセルを処理します。

swift
for try await item in asyncSequence {
    print(item)
}
// Custom AsyncSequence
struct Counter: AsyncSequence {
    struct AsyncIterator: AsyncIteratorProtocol {
        var current = 0
        mutating func next() async -> Int? {
            current += 1
            return current <= 5 ? current : nil
        }
    }
    func makeAsyncIterator() -> AsyncIterator { AsyncIterator() }
}

タスクグループ

タスクグループは複数のタスクを並行実行し結果を収集します。addTask が子タスクを追加します。for await が完了した結果を反復します。すべてのタスクはグループが戻る前に完了する必要があります。構造化並行性:キャンセルは子に伝播します。結果は安全に収集されます。

swift
let results = await withTaskGroup(of: Int.self) { group in
    for i in 1...5 {
        group.addTask { i * i }
    }
    var sum = 0
    for await result in group {
        sum += result
    }
    return sum
}

継続

withCheckedContinuation がコンプリーションハンドラ API を async/await に橋渡しします。resume(returning:) が非同期関数を再開します。正確に1回呼び出す必要があります。withCheckedThrowingContinuation はエラーをサポートします。既存の API で async/await を採用するのに有用です。

swift
func fetchWithCompletion(_ completion: @escaping (String) -> Void) {
    // Legacy completion handler API
    completion("data")
}
// Wrap in async
func fetchAsync() async -> String {
    await withCheckedContinuation { continuation in
        fetchWithCompletion { result in
            continuation.resume(returning: result)
        }
    }
}
23

SwiftUI 深掘り

ビューモディファイア

モディファイアはビューをラップして外観や動作を変更します。順序が重要です:後のモディファイアが前のものをラップします。.background の前に .padding を置くとパディングが背景の内側になります。モディファイアは新しい View インスタンスを返します。複雑なスタイリングのためにチェーンします。

swift
Text("Hello")
    .font(.title)
    .foregroundColor(.blue)
    .padding()
    .background(Color.gray)
    .cornerRadius(8)
    .shadow(radius: 4)

リスト

List はスクロール可能な行を表示します。ForEach がデータから行を生成します。onDelete がスワイプ削除を有効にします。他のモディファイア:onMove、onInsert。ForEach には Identifiable なデータ(id プロパティ)が必要です。リストスタイル:.plain、.insetGrouped、.sidebar。

swift
List {
    ForEach(items) { item in
        HStack {
            Text(item.name)
            Spacer()
            Text("\(item.price)")
        }
    }
    .onDelete { indexSet in
        items.remove(atOffsets: indexSet)
    }
}
.listStyle(.insetGrouped)

ナビゲーション

NavigationStack がビューのスタックを管理します。NavigationLink が宛先をプッシュします。navigationTitle がタイトルを設定します。toolbar がナビゲーションバーボタンを追加します。iOS 16 以降用。古いバージョンには NavigationView を使用してください。シートとアラートは .sheet と .alert モディファイアを使用します。

swift
NavigationStack {
    List(items) { item in
        NavigationLink(item.name) {
            DetailView(item: item)
        }
    }
    .navigationTitle("Items")
    .toolbar {
        ToolbarItem(placement: .navigationBarTrailing) {
            Button("Add") { addItem() }
        }
    }
}

状態管理

@State はローカルビュー状態(値型)用です。@Binding は渡された状態用です。@StateObject は所有する監視可能オブジェクト(一度作成)用です。@ObservedObject は外部の監視可能オブジェクト用です。@EnvironmentObject はアプリ全体の状態用です。@State の変更がビュー更新をトリガーします。

swift
struct CounterView: View {
    @State private var count = 0
    @Binding var selected: Int
    @StateObject var viewModel = ViewModel()
    var body: some View {
        Button("\(count)") { count += 1 }
    }
}

アニメーション

withAnimation が状態変更をアニメーション化します。.spring、.easeInOut はアニメーションカーブです。.animation(value:) は値が変更されたときにアニメーション化します。.scaleEffect、.opacity、.offset はアニメーション可能です。暗黙的アニメーションは .animation モディファイアを使用します。明示的には withAnimation を使用します。

swift
struct AnimatedView: View {
    @State private var scale: CGFloat = 1.0
    var body: some View {
        Button("Tap") {
            withAnimation(.spring(response: 0.3)) {
                scale = scale == 1 ? 1.5 : 1
            }
        }
        .scaleEffect(scale)
        .animation(.easeInOut(duration: 0.3), value: scale)
    }
}
24

Combine フレームワーク

パブリッシャーとサブスクライバー

Combine は Apple のリアクティブフレームワークです。パブリッシャーが時間とともに値を発行します。サブスクライバーが受け取ります。sink がサブスクライバーを作成します。Just が単一の値を発行します。PassthroughSubject は手動パブリッシャーです。Cancellable は保持しないとサブスクリプションがキャンセルされます。RxSwift に似ています。

swift
import Combine

let publisher = Just(42)
let cancellable = publisher.sink { value in
    print("Received: \(value)")
}
// Just emits one value then finishes
// PassthroughSubject: manual values
let subject = PassthroughSubject<Int, Never>()
subject.send(1)
subject.send(2)

演算子

演算子はパブリッシャーの出力を変換します。map が値を変換し、filter が値を選択し、debounce が静寂まで遅延し、throttle がレートを制限し、combineLatest がストリームを結合し、flatMap がパブリッシャーをチェーンします。演算子は遅延です:sink がサブスクライブするまで何も起こりません。

swift
let publisher = (1...10).publisher
    .map { $0 * 2 }
    .filter { $0 > 10 }
    .sink { print($0) }  // 12, 14, 16, 18, 20

// Debounce
let debounced = subject
    .debounce(for: .seconds(0.5), scheduler: RunLoop.main)
    .sink { print($0) }

@Published

@Published はプロパティを Combine パブリッシャーとして公開します。$ プレフィックスでパブリッシャーにアクセスします。変更が新しい値を発行します。SwiftUI のために ObservableObject と連携します。パブリッシャーはサブスクリプション時に現在値を発行します。手動通知なしのリアクティブ UI 更新に有用です。

swift
class ViewModel: ObservableObject {
    @Published var count = 0
}
let vm = ViewModel()
let cancellable = vm.$count.sink { newCount in
    print("Count changed: \(newCount)")
}
vm.count = 1  // Prints "Count changed: 1"
vm.count = 2  // Prints "Count changed: 2"

Future

Future はコンプリーションハンドラ API をパブリッシャーとしてラップします。promise(.success) が値を発行し、promise(.failure) がエラーを発行します。Future は正確に1回発行します。コールバック API を Combine に橋渡しするのに有用です。クロージャは .delay または deferred でラップしない限り即座に実行されます。

swift
func fetchUser() -> Future<User, Error> {
    Future { promise in
        URLSession.shared.dataTask(with: url) { data, _, error in
            if let error = error { promise(.failure(error)) }
            else { promise(.success(parse(data!))) }
        }.resume()
    }
}
// Usage
fetchUser().sink(receiveCompletion: { _ in }, receiveValue: { user in print(user) })

エラー処理

Fail は即座にエラーを発行します。catch が失敗したパブリッシャーを別のものに置き換えます。retry が失敗時に再サブスクライブします。assertNoFailure はエラー時にクラッシュします(デバッグ用)。completion イベントが値の終了を通知します。エラーはキャッチされない限り下流に伝播します。

swift
let publisher = Fail<Int, MyError>(error: .notFound)
    .catch { error in
        Just(0)  // Fallback value
    }
    .sink { print($0) }  // 0

// retry on failure
let retried = requestPublisher
    .retry(3)
    .sink(receiveCompletion: { _ in }, receiveValue: { _ in })
25

メモリ管理

ARC の基礎

ARC(Automatic Reference Counting)は参照を追跡します。カウントがゼロになると deinit が実行されメモリが解放されます。強参照がカウントをインクリメントします。参照サイクルは解放を防ぎます。ARC は決定的です(GC とは異なります)。最後の参照が解放されると deinit が同期的に実行されます。

swift
class Person {
    let name: String
    init(name: String) { self.name = name }
    deinit { print("\(name) is being deinitialized") }
}
var p1: Person? = Person(name: "Alice")
p1 = nil  // Prints "Alice is being deinitialized"

弱参照

weak 参照は保持カウントをインクリメントしません。オブジェクトが解放されると自動的に nil になります。optional var である必要があります。サイクルを破るためデリゲートとオブザーバーに使用します。weak 参照はゼロ化されます:オブジェクトがなくなった後も安全にアクセスできます。保持サイクルの最も一般的な修正です。

swift
class View {
    weak var delegate: Delegate?
}
class Delegate {
    var view: View?  // strong
}
// weak: does not keep object alive, auto-set to nil
// Use for delegate patterns to avoid cycles

非所有参照

unowned 参照は保持カウントをインクリメントしません。weak とは異なり、非オプショナルでゼロ化されません。解放後にアクセスするとクラッシュします。参照先のオブジェクトが参照元より長く存続するか同時に終了する場合に使用してください。weak より高速です(nil チェックなし)。self がクロージャより長く存続する場合のクロージャの self キャプチャで一般的です。

swift
class Customer {
    var card: CreditCard?
}
class CreditCard {
    unowned let customer: Customer
    init(customer: Customer) { self.customer = customer }
}
// unowned: does not keep alive, but not optional
// CRASHES if accessed after dealloc
// Use when the other object has same or shorter lifetime

保持サイクル

保持サイクルはオブジェクトが互いに強参照し合うときに発生します。どちらも解放されず、メモリリークします。weak または unowned で修正します。デリゲート、オブザーバー、クロージャで一般的です。クロージャはデフォルトで self を強くキャプチャします。[weak self] または [unowned self] がサイクルを破ります。Memory Graph Debugger でサイクルを見つけてください。

swift
// BAD: retain cycle
class Node {
    var next: Node?
    var prev: Node?  // strong -> cycle
}
// GOOD: break with weak
class Node {
    var next: Node?
    weak var prev: Node?
}
// Closures: use [weak self]
{ [weak self] in self?.update() }

クロージャとキャプチャ

クロージャは変数を参照でキャプチャします。格納クロージャ(escaping)は self をキャプチャすると保持サイクルを作成します。[weak self] がキャプチャをオプショナルで弱くします。@escaping は関数呼び出しより長く存続するクロージャをマークします。非 escaping クロージャ(デフォルト)はサイクルを引き起こしません。コンパイラが潜在的なサイクルについて警告します。

swift
class Timer {
    var handler: (() -> Void)?
    func start() {
        handler = { [weak self] in
            self?.tick()  // weak to avoid cycle
        }
    }
    func tick() { /* ... */ }
}
// Escaping closure: stored for later
// @escaping marks escaping closures
26

エラー処理 深掘り

カスタムエラー

カスタムエラーは Error に準拠します。LocalizedError が errorDescription を提供します。関連値がコンテキストを運びます。列挙型がエラーを定義する慣用的な方法です。各ケースが個別の失敗を表します。switch の網羅性がすべてのエラーが処理されることを保証します。デバッグ出力のために CustomStringConvertible に準拠してください。

swift
enum APIError: Error, LocalizedError {
    case invalidURL
    case unauthorized
    case serverError(Int)
    case decodingFailed(Error)

    var errorDescription: String? {
        switch self {
        case .invalidURL: return "Invalid URL"
        case .unauthorized: return "Unauthorized"
        case .serverError(let code): return "Server error: \(code)"
        case .decodingFailed(let err): return "Decode failed: \(err)"
        }
    }
}

Result 型

Result は型付きの成功/失敗列挙型です。失敗する可能性のある同期操作に有用です。map が成功値を変換し、flatMap が操作をチェーンします。get() は try/catch に変換するためにスローします。Result は保存または渡される結果にはスロー関数より推奨されます。エラー処理と値セマンティクスを組み合わせます。

swift
func fetchUser(id: Int) -> Result<User, APIError> {
    guard id > 0 else { return .failure(.invalidURL) }
    return .success(User(name: "Alice"))
}
// Usage
switch fetchUser(id: 1) {
case .success(let user): print(user)
case .failure(let error): print(error)
}
// map and flatMap
let result = fetchUser(id: 1).map { $0.name }

Rethrows

rethrows はクロージャパラメータからエラーを伝播します。クロージャがスローしない場合、関数もスローしません。これにより非スロークロージャを渡す呼び出し側に try を強制することを避けます。map、filter、その他の高階関数で使用されます。関数自体はクロージャとは独立してスローできません。

swift
func withLock<T>(_ lock: NSLock, _ body: () throws -> T) rethrows -> T {
    lock.lock()
    defer { lock.unlock() }
    return try body()
}
// Only throws if body throws
let x = withLock(lock) { 5 }  // no try needed
let y = try withLock(lock) { try throwingFunc() }

エラー伝播

try はエラーを呼び出し側に伝播します。try? はエラーを nil に変換します(Optional を返します)。try! はエラー時にクラッシュします(確信がある場合に使用)。エラーはキャッチされるまでコールスタックを遡ります。スローする関数は throws をマークする必要があります。コンパイラが try マーカーを強制し、未処理のエラーを防ぎます。defer はスローされたエラーに関わらず実行されます。

swift
func process() throws {
    let data = try fetchData()  // may throw
    let user = try parse(data)  // may throw
    save(user)                  // does not throw
}
// try?: converts to optional, nil on error
let user = try? fetchUser()
// try!: asserts no error, crashes if thrown
let user = try! fetchUser()

defer によるクリーンアップ

defer はスコープ退出時に実行するクリーンアップをスケジュールします。スコープがどう退出しても(return、throw、フォールスルー)実行されます。複数の defer は LIFO 順で実行されます。ファイルのクローズ、ロックの解放、リソースの解放に有用です。defer は throw や break/continue できません。クロージャは変数を参照でキャプチャします。

swift
func processFile() throws {
    let file = open("file.txt")
    defer { close(file) }  // runs on scope exit

    let data = try read(file)
    // ... process ...
    // close() runs here even if read throws
}
// Multiple defers run in reverse order (LIFO)
defer { print("1") }
defer { print("2") }  // prints 2 then 1

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.