変数・型・リテラル
基礎let は再代入不可の定数、var は変更可能な変数。コンパイラが右辺から型を推論するため、型注釈は省略できる。
let pi = 3.14159 // Double と推論
var counter: Int = 0 // 明示的な型注釈
counter += 1
// pi = 3.0 // コンパイルエラー(定数への再代入)Swift の数値型は Int・Double・Float、論理値は Bool、テキストは String・Character。すべて構造体として実装されている。
let age: Int = 25
let height: Double = 175.5
let isStudent: Bool = true
let greeting: String = "Hello, Swift!"
let initial: Character = "S"
// 型変換は明示的に行う
let doubled = Double(age) * 2.0複数の値を一つにまとめる軽量な型。ラベルを付けることで名前付きアクセスが可能。関数の複数戻り値によく使われる。
// ラベルなし
let point = (3, 4)
print(point.0, point.1) // 3 4
// ラベルあり
let status = (code: 200, message: "OK")
print(status.code) // 200
print(status.message) // OK
// 分割代入
let (x, y) = point
print(x, y) // 3 4既存の型に別名を付ける。複雑な型を短い名前で扱えるようになり、ドメイン語彙をコードに反映できる。
typealias UserID = Int
typealias Completion = (Result<String, Error>) -> Void
func fetchUser(id: UserID, completion: Completion) {
// ...
}
// タプルにもエイリアスを付けられる
typealias Vector2D = (x: Double, y: Double)
let velocity: Vector2D = (x: 1.0, y: -0.5)Optional
基礎T? は Optional の糖衣構文。値が存在しない状態(nil)を型システムで表現する。非 Optional 型には nil を代入できない。
var name: String? = "Alice"
name = nil // OK:Optional 型なので nil 代入可能
var id: Int = 42
// id = nil // コンパイルエラー:非 Optional 型
// Optional の内部表現
let opt: Optional<Int> = .some(10)
let none: Optional<Int> = .noneオプショナルバインディングで安全に値を取り出す。guard let は早期リターンパターンに適し、以降のスコープで値を使い続けられる。
let input: String? = "42"
// if let
if let value = input {
print("値: (value)")
} else {
print("nil でした")
}
// guard let(早期リターン)
func parse(_ s: String?) -> Int {
guard let s = s, let n = Int(s) else { return 0 }
return n // s・n をここで使える
}
// Swift 5.7+ の省略記法
if let input {
print(input) // 同名変数でバインド
}?? は Optional が nil のときデフォルト値を返す。! は強制アンラップで、nil のとき実行時クラッシュするため慎重に使う。
let username: String? = nil
let display = username ?? "ゲスト" // "ゲスト"
// チェーン
let a: Int? = nil
let b: Int? = 5
let result = a ?? b ?? 0 // 5
// 強制アンラップ(nil でないことが確実な場合のみ)
let forced = username! // nil なら実行時エラー! による強制アンラップは nil が確実に存在しない場面に限定すること。防御的コードには if let や ?? を使う。?. でプロパティ・メソッド・添字アクセスを連鎖させる。チェーン中に nil があれば即 nil を返すため、ネストした if let を省ける。
struct Address { var city: String }
struct User { var address: Address? }
let user: User? = User(address: Address(city: "Tokyo"))
// Optional チェーン
let city = user?.address?.city // Optional<String>
print(city ?? "不明") // Tokyo
// メソッド呼び出し
let upper = user?.address?.city.uppercased()
// user が nil なら upper == nil制御フロー
基礎Swift の switch はデフォルトで fallthrough しない。enum を対象にすると全 case を網羅しないとコンパイルエラーになる。
enum Direction { case north, south, east, west }
let dir = Direction.north
switch dir {
case .north: print("北")
case .south: print("南")
case .east: print("東")
case .west: print("西")
// default 不要:全 case 網羅済み
}
// 式として使える(Swift 5.9+)
let label = switch dir {
case .north: "N"
case .south: "S"
case .east: "E"
case .west: "W"
}for-in は範囲・配列・辞書などシーケンスを反復する。repeat-while は C の do-while に相当し、少なくとも一度ループ本体を実行する。
// 範囲(半開区間・閉区間)
for i in 0..<5 { print(i) } // 0,1,2,3,4
for i in 1...3 { print(i) } // 1,2,3
// 辞書
let scores = ["Alice": 90, "Bob": 75]
for (name, score) in scores {
print("(name): (score)")
}
// repeat-while
var x = 0
repeat { x += 1 } while x < 3for-in や switch の case に where 句を追加してフィルタ条件を記述できる。ループ内の if を外に出してコードを平坦にできる。
let numbers = [1, -2, 3, -4, 5]
// for-in + where
for n in numbers where n > 0 {
print(n) // 1, 3, 5
}
// switch + where
let pair = (2, 3)
switch pair {
case let (a, b) where a + b > 4:
print("合計 > 4") // ここに来る
default:
print("それ以外")
}Swift の switch は値・型・範囲・タプル・enum の associated values など多彩なパターンに対応する。case let で値を変数に束縛できる。
enum Shape {
case circle(radius: Double)
case rect(width: Double, height: Double)
}
let s = Shape.circle(radius: 5.0)
switch s {
case .circle(let r) where r > 3:
print("大きい円: (r)")
case .circle(let r):
print("小さい円: (r)")
case let .rect(w, h):
print("長方形 (w)x(h)")
}
// 範囲パターン
let score = 85
switch score {
case 90...100: print("A")
case 80..<90: print("B")
default: print("C以下")
}関数
基礎呼び出し側に見える「引数ラベル(外部名)」と関数内で使う「引数名(内部名)」を別々に指定できる。_ でラベルを省略可能。
func greet(to name: String) -> String {
return "Hello, (name)!"
}
print(greet(to: "Alice")) // Hello, Alice!
// ラベル省略
func add(_ a: Int, _ b: Int) -> Int { a + b }
print(add(1, 2)) // 3
// デフォルト引数
func log(_ msg: String, level: String = "INFO") {
print("[(level)] (msg)")
}
log("起動完了") // [INFO] 起動完了
log("エラー", level: "ERROR")... で可変長引数を宣言すると、引数はその型の配列として関数内で扱える。@discardableResult を付けると戻り値を無視してもコンパイル警告が出ない。
func sum(_ nums: Int...) -> Int {
nums.reduce(0, +)
}
print(sum(1, 2, 3, 4)) // 10
@discardableResult
func save(_ data: String) -> Bool {
// 保存処理
return true
}
save("data") // 戻り値を無視しても警告なし関数は第一級市民。関数型 (A, B) -> C として変数に代入・引数として渡せる。末尾クロージャ構文(trailing closure)でコールバックを簡潔に書ける。
// 関数型を変数に代入
let double: (Int) -> Int = { $0 * 2 }
print(double(5)) // 10
// 高階関数
let nums = [1, 2, 3, 4]
let doubled = nums.map { $0 * 2 } // [2, 4, 6, 8]
// trailing closure
func doAsync(work: () -> Void) { work() }
doAsync {
print("非同期っぽい処理")
}
// クロージャの省略記法(引数名 → $0 $1)
let sorted = nums.sorted { $0 > $1 } // [4, 3, 2, 1]inout を付けると値型(struct 等)を参照渡しできる。呼び出し時に & を付ける。コピーではなく元の変数を変更したい場合に使う。
func increment(_ n: inout Int, by step: Int = 1) {
n += step
}
var count = 10
increment(&count) // count == 11
increment(&count, by: 5) // count == 16
print(count) // 16コレクション
基礎3 つの基本コレクション型。いずれも var で変更可能、let で不変。Array は順序付き、Set は順序なし重複なし、Dictionary はキーと値のマッピング。
// Array
var fruits = ["apple", "banana", "cherry"]
fruits.append("date")
fruits.remove(at: 1) // ["apple", "cherry", "date"]
// Dictionary
var scores: [String: Int] = ["Alice": 90, "Bob": 75]
scores["Charlie"] = 85
let bScore = scores["Bob"] ?? 0 // 75
// Set
var tags: Set = ["swift", "ios", "apple"]
tags.insert("macos")
print(tags.contains("swift")) // trueSwift コレクションは豊富な高階関数を持つ。compactMap は Optional を変換して nil を除去、flatMap はネストした配列を平坦化する。
let nums = [1, 2, 3, 4, 5]
let squared = nums.map { $0 * $0 } // [1,4,9,16,25]
let evens = nums.filter { $0 % 2 == 0 } // [2,4]
let total = nums.reduce(0, +) // 15
// compactMap: Optional 変換 + nil 除去
let strs = ["1", "two", "3"]
let ints = strs.compactMap { Int($0) } // [1, 3]
// flatMap: ネスト配列を平坦化
let nested = [[1,2],[3,4],[5]]
let flat = nested.flatMap { $0 } // [1,2,3,4,5]enumerated() でインデックスと値を同時に取得。zip で 2 つのシーケンスを対にして反復できる。タプルの分割代入と組み合わせると表現力が高い。
let letters = ["a", "b", "c"]
// enumerated でインデックス付き反復
for (i, ch) in letters.enumerated() {
print("(i): (ch)")
}
// zip
let nums = [1, 2, 3]
for (letter, num) in zip(letters, nums) {
print("(letter)-(num)") // a-1, b-2, c-3
}文字列
基礎"\(式)" 構文で文字列の中に任意の式を埋め込める。CustomStringConvertible を採用した型も直接埋め込める。
let name = "Swift"
let version = 5.10
print("言語: \(name), バージョン: \(version)")
// 言語: Swift, バージョン: 5.1
// 式も直接書ける
let a = 3, b = 4
print("斜辺: \((a*a + b*b).squareRoot())") // 5.0"""...""" で複数行の文字列を書ける。終端の """ のインデント位置が各行の基準となり、共通インデントは除去される。
let html = """
<html>
<body>Hello</body>
</html>
"""
// インデントは終端 """ の位置が基準
print(html)Swift の String は Unicode 対応のため、整数インデックスで文字にアクセスできない。String.Index を使って位置を表現する必要がある。
let s = "Hello, 世界"
let start = s.startIndex
let end = s.endIndex
// インデックス操作
let idx = s.index(start, offsetBy: 7)
print(s[idx]) // 世
// 文字数
print(s.count) // 9(絵文字なども 1 文字)
// String.Index を Int に直接変換する API はないs[2] のような整数インデックスはコンパイルエラー。index(_:offsetBy:) を使う。== は内容比較。hasPrefix・hasSuffix・contains でパターン検索。range(of:) でサブ文字列の位置を取得できる。
let str = "Swift is awesome"
print(str.hasPrefix("Swift")) // true
print(str.hasSuffix("awesome")) // true
print(str.contains("is")) // true
// 大文字・小文字無視の比較
let eq = str.lowercased() == "swift is awesome"
print(eq) // true
// サブ文字列の位置
if let range = str.range(of: "is") {
print(str[range]) // is
}構造体・クラス・列挙型
基礎struct は値型(代入・引数渡しでコピー)、class は参照型(同一インスタンスを共有)。Swift は struct 優先を推奨し、継承が必要な場合のみ class を使う。
struct PointS { var x: Int; var y: Int }
class PointC { var x: Int; var y: Int
init(_ x: Int, _ y: Int) { self.x = x; self.y = y }
}
var s1 = PointS(x: 1, y: 2)
var s2 = s1 // コピー
s2.x = 99
print(s1.x) // 1(変わらない)
var c1 = PointC(1, 2)
var c2 = c1 // 同じ参照
c2.x = 99
print(c1.x) // 99(変わる)enum のケースに値を付加する associated values(関連値)が Swift の強力な機能。switch で分解して型安全に扱える。
enum NetworkResult {
case success(data: Data)
case failure(code: Int, message: String)
}
let result = NetworkResult.failure(code: 404, message: "Not Found")
switch result {
case .success(let data):
print("データ: \(data)")
case .failure(let code, let msg):
print("エラー \(code): \(msg)")
}値型(struct・enum)のメソッドが self を変更するには mutating を明示する必要がある。let 変数に格納されたインスタンスでは mutating メソッドを呼べない。
struct Counter {
var count = 0
mutating func increment() { count += 1 }
mutating func reset() { count = 0 }
}
var c = Counter()
c.increment() // OK:var なので mutating 可
c.increment()
print(c.count) // 2
let fixed = Counter()
// fixed.increment() // コンパイルエラーCaseIterable を採用すると allCases で全ケースを配列として取得できる。RawRepresentable(Int や String 等)を採用するとケースに生の値を付けられる。
enum Season: String, CaseIterable {
case spring = "春"
case summer = "夏"
case autumn = "秋"
case winter = "冬"
}
// allCases
for s in Season.allCases { print(s.rawValue) }
// rawValue → enum
if let s = Season(rawValue: "夏") {
print(s) // summer
}プロトコル
基礎プロトコルはメソッド・プロパティの要件を定義する「インターフェース」。struct・class・enum がコロン(:)で採用し、要件を実装する。
protocol Drawable {
var color: String { get }
func draw()
}
struct Circle: Drawable {
var color: String
func draw() { print("○ \(color)") }
}
struct Square: Drawable {
var color: String
func draw() { print("□ \(color)") }
}
let shapes: [Drawable] = [Circle(color: "赤"), Square(color: "青")]
shapes.forEach { $0.draw() }extension でプロトコルのデフォルト実装を提供できる。採用型はオーバーライドしなければデフォルト実装が使われ、Mix-in のような再利用が可能。
protocol Greetable {
var name: String { get }
func greet() -> String
}
extension Greetable {
// デフォルト実装
func greet() -> String { "こんにちは、\(name)!" }
}
struct User: Greetable { var name: String }
struct Bot: Greetable {
var name: String
// オーバーライド
func greet() -> String { "Bot:\(name) です" }
}
print(User(name: "Alice").greet()) // こんにちは、Alice!
print(Bot(name: "HAL").greet()) // Bot:HAL ですProtocol1 & Protocol2 で複数プロトコルを同時に要求する型を表現できる。型エイリアスや関数引数の型注釈として使える。
protocol Named { var name: String { get } }
protocol Aged { var age: Int { get } }
typealias Person = Named & Aged
func introduce(_ p: Person) {
print("\(p.name), \(p.age)歳")
}
struct Student: Named, Aged {
var name: String
var age: Int
}
introduce(Student(name: "Bob", age: 20))クラス継承の代わりにプロトコルと extension の合成で機能を組み立てるパラダイム。多重継承の問題なく横断的な機能追加が可能で、値型にも適用できる。
protocol Serializable {
func toJSON() -> String
}
protocol Loggable {
func log()
}
// Serializable を採用したすべての型に Loggable デフォルト実装を付与
extension Loggable where Self: Serializable {
func log() { print("[LOG] \(toJSON())") }
}
struct Config: Serializable, Loggable {
var key: String
func toJSON() -> String { "{"key":\"\(key)\"}" }
}
Config(key: "debug").log()ジェネリクス
基礎 で型パラメータを宣言し、T: Equatable のように制約を付けると特定のプロトコルを要求できる。型安全なコードの再利用を実現する。
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
for (i, v) in array.enumerated() {
if v == value { return i }
}
return nil
}
print(findIndex(of: 3, in: [1,2,3,4])) // Optional(2)
print(findIndex(of: "b", in: ["a","b"])) // Optional(1)ジェネリック型パラメータを持つ struct・class・enum を定義できる。標準ライブラリの Array・Dictionary もジェネリック型として実装されている。
struct Stack<Element> {
private var storage: [Element] = []
mutating func push(_ item: Element) { storage.append(item) }
mutating func pop() -> Element? { storage.popLast() }
var top: Element? { storage.last }
}
var stack = Stack<Int>()
stack.push(1)
stack.push(2)
print(stack.pop()!) // 2
print(stack.top!) // 1プロトコルで associatedtype を宣言すると、採用型が具体的な型を提供する「プロトコルのジェネリクス」を実現できる。Sequence や Collection も内部でこれを使用。
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
struct IntBox: Container {
private var items: [Int] = []
mutating func append(_ item: Int) { items.append(item) }
var count: Int { items.count }
subscript(i: Int) -> Int { items[i] }
}some Protocol は「このプロトコルを採用するある一つの具体型」(opaque type)。any Protocol は型消去された existential。some の方が型情報を保持し高効率。
protocol Animal { func sound() -> String }
struct Dog: Animal { func sound() -> String { "ワン" } }
struct Cat: Animal { func sound() -> String { "ニャー" } }
// some: 戻り値は常に同じ具体型(コンパイラが推論)
func makeDog() -> some Animal { Dog() }
// any: 異なる具体型を混在させたい場合
func makeAnimal(_ isCat: Bool) -> any Animal {
isCat ? Cat() : Dog()
}
print(makeDog().sound()) // ワン
print(makeAnimal(true).sound()) // ニャー拡張・演算子・サブスクリプト
基礎extension で既存の型(自作・標準ライブラリ・サードパーティ)にメソッド・プロパティ・イニシャライザを追加できる。継承せずに機能を拡張できる。
extension Int {
var isEven: Bool { self % 2 == 0 }
func times(_ block: () -> Void) {
for _ in 0..<self { block() }
}
}
print(4.isEven) // true
3.times { print("Hello") } // 3回出力
extension String {
var trimmed: String { trimmingCharacters(in: .whitespaces) }
}
print(" hello ".trimmed) // "hello"operator キーワードで独自の演算子を定義できる。infix(二項)・prefix(前置)・postfix(後置)を指定する。DSL 構築などに使われる。
infix operator **: MultiplicationPrecedence
func ** (base: Double, exp: Double) -> Double {
pow(base, exp)
}
print(2.0 ** 10.0) // 1024.0
// prefix 演算子
prefix operator √
prefix func √(_ n: Double) -> Double { n.squareRoot() }
print(√16.0) // 4.0subscript で obj[key] スタイルのアクセスを自作型に定義できる。複数の引数を取るオーバーロードも可能。
struct Matrix {
private var grid: [Double]
let rows: Int, cols: Int
init(rows: Int, cols: Int) {
self.rows = rows; self.cols = cols
grid = Array(repeating: 0, count: rows * cols)
}
subscript(row: Int, col: Int) -> Double {
get { grid[row * cols + col] }
set { grid[row * cols + col] = newValue }
}
}
var m = Matrix(rows: 2, cols: 2)
m[0, 1] = 3.5
print(m[0, 1]) // 3.5@propertyWrapper でプロパティの読み書きに共通ロジックを注入できる。SwiftUI の @State・@Binding もこれを使って実装されている。
@propertyWrapper
struct Clamped<T: Comparable> {
private var value: T
let range: ClosedRange<T>
init(wrappedValue: T, _ range: ClosedRange<T>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: T {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
}
struct Config {
@Clamped(0...100) var volume: Int = 50
}
var cfg = Config()
cfg.volume = 150
print(cfg.volume) // 100(クランプされる)エラーハンドリング
基礎Error プロトコルに準拠した値を throw して通知し、try で呼び出し、catch でハンドリング。Swift のエラーは型付きで、enum との相性が良い。
enum ParseError: Error {
case emptyInput
case invalidFormat(String)
}
func parseAge(_ s: String) throws -> Int {
guard !s.isEmpty else { throw ParseError.emptyInput }
guard let n = Int(s) else { throw ParseError.invalidFormat(s) }
return n
}
do {
let age = try parseAge("abc")
print(age)
} catch ParseError.emptyInput {
print("入力が空です")
} catch ParseError.invalidFormat(let v) {
print("不正な値: \(v)")
} catch {
print("不明なエラー: \(error)")
}try? は失敗時に nil を返す Optional に変換。try! はエラー時に実行時クラッシュ。失敗しないことが確実な場合か、プロトタイプで try! を使う。
// try?
let age1 = try? parseAge("25") // Optional(25)
let age2 = try? parseAge("abc") // nil
// ?? と組み合わせ
let display = (try? parseAge("30")) ?? -1 // 30
// try!(失敗しないことが確実な場合のみ)
let regex = try! NSRegularExpression(pattern: "\\d+")defer ブロックはスコープを抜けるときに必ず実行される(return・throw・fall-through に関わらず)。ファイルクローズやロック解除などのクリーンアップに使う。
func readFile(_ path: String) throws -> String {
let file = openFile(path) // 仮の関数
defer { closeFile(file) } // 必ず実行
if file == nil { throw ParseError.emptyInput }
return readContents(file!)
}
// 複数 defer はLIFO順(後入れ先出し)
func demo() {
defer { print("3") }
defer { print("2") }
defer { print("1") }
print("実行中")
}
// 出力: 実行中 → 1 → 2 → 3Result は成功値か失敗値のどちらかを保持する enum。コールバックベースの非同期 API で throws の代わりに使われることが多い。
func divide(_ a: Double, by b: Double) -> Result<Double, Error> {
guard b != 0 else {
return .failure(ParseError.invalidFormat("除数が0"))
}
return .success(a / b)
}
let result = divide(10, by: 2)
switch result {
case .success(let v): print("結果: \(v)") // 5.0
case .failure(let e): print("エラー: \(e)")
}
// get() で throws に変換
let value = try? result.get() // Optional(5.0)ARC・メモリ管理
応用Swift はコンパイル時に参照カウント操作を挿入し、参照数が 0 になったオブジェクトを自動解放する。GC(ガベージコレクタ)と異なり停止時間がない。
class Person {
let name: String
init(name: String) { self.name = name; print("\(name) 生成") }
deinit { print("\(name) 解放") }
}
do {
let a = Person(name: "Alice") // 参照カウント: 1
let b = a // 参照カウント: 2
_ = b
} // スコープを抜けると参照カウント: 0 → deinit 呼ばれる
// 出力: Alice 生成 → Alice 解放weak var は Optional で参照先が解放されると自動的に nil になる。unowned は非 Optional で参照先の生存を前提とする。どちらも参照カウントを増やさない。
class Owner {
var pet: Pet?
}
class Pet {
weak var owner: Owner? // 循環参照を防ぐ
}
var o: Owner? = Owner()
var p: Pet? = Pet()
o?.pet = p
p?.owner = o
o = nil // Owner の参照カウントが 0 に
// p?.owner は nil になる(weak なので)
print(p?.owner == nil) // trueクロージャが self を強参照するとクラスとの間で循環参照が生まれる。[weak self] または [unowned self] でキャプチャリストを指定して防ぐ。
class Timer {
var callback: (() -> Void)?
func start() {
// [weak self] でキャプチャ
callback = { [weak self] in
guard let self = self else { return }
print("tick: \(self)")
}
}
deinit { print("Timer 解放") }
}
var t: Timer? = Timer()
t?.start()
t?.callback?()
t = nil // "Timer 解放" が出力される[unowned self] は参照先が必ず生存している確信がある場合のみ使う。nil になり得る場合は必ず [weak self] を選ぶ。Swift は実行時にメモリへの排他アクセスを強制する。同じ変数を inout で渡しながら同時に別途アクセスすることはコンパイルエラーまたは実行時エラーになる。
func increment(_ n: inout Int) { n += 1 }
var value = 10
increment(&value) // OK
// 競合例(コンパイルエラー)
// increment(&value) ← 同時に value を別途参照するコードと組み合わせると不可
// struct のプロパティへの inout も排他制御される
struct Buffer {
var data: [Int] = [1,2,3]
mutating func process() {
data.sort() // OK: self 全体を mutating で保護
}
}Swift Concurrency
応用async 関数は中断(suspend)可能な非同期関数。await で一時停止し、再開後に結果を受け取る。コールバック地獄を排除してシーケンシャルなコードが書ける。
import Foundation
func fetchUsername(for id: Int) async throws -> String {
let url = URL(string: "https://example.com/users/\(id)")!
let (data, _) = try await URLSession.shared.data(from: url)
let user = try JSONDecoder().decode(User.self, from: data)
return user.name
}
// 呼び出し側(async コンテキスト内)
Task {
do {
let name = try await fetchUsername(for: 1)
print("ユーザー: \(name)")
} catch {
print("エラー: \(error)")
}
}Task で async コンテキスト外から非同期処理を起動。withTaskGroup で動的な数のタスクを並列実行し、結果を収集できる。
// Task: 単独タスクの起動
let task = Task {
try await fetchUsername(for: 42)
}
let name = try await task.value
// withTaskGroup: 並列実行して結果を収集
let ids = [1, 2, 3, 4, 5]
let names = try await withTaskGroup(of: String.self) { group in
for id in ids {
group.addTask { try await fetchUsername(for: id) }
}
var result: [String] = []
for try await n in group { result.append(n) }
return result
}actor は参照型のうちプロパティへのアクセスを直列化(serialized)するもの。複数タスクからの同時アクセスによるデータ競合をコンパイラが防止する。
actor Counter {
private(set) var value = 0
func increment() { value += 1 }
func reset() { value = 0 }
}
let counter = Counter()
await withTaskGroup(of: Void.self) { group in
for _ in 0..<1000 {
group.addTask { await counter.increment() }
}
}
// actor が直列化するのでデータ競合なし
print(await counter.value) // 1000@MainActor を付けたクラス・関数はメインスレッドで実行が保証される。UIKit/SwiftUI の更新はメインスレッドで行う必要があり、コンパイラが強制できる。
@MainActor
class ViewModel: ObservableObject {
@Published var title = "読み込み中..."
func load() async {
let result = await fetchData() // バックグラウンドで実行
title = result // @MainActor なのでメインスレッド
}
}
// 関数単位で指定
@MainActor func updateUI(with text: String) {
label.text = text
}Structured Concurrency・AsyncSequence
応用async let で複数の非同期処理を同時に開始し、後で await で結果を受け取る。withTaskGroup より少数の並列タスクを静的に記述する場合に簡潔。
func loadDashboard() async throws -> (String, [Int]) {
// username と scores を並列で取得
async let username = fetchUsername(for: 1)
async let scores = fetchScores(for: 1)
// ここで両方を await(どちらか遅い方を待つ)
return try await (username, scores)
}
let (name, scores) = try await loadDashboard()
print(name, scores)AsyncSequence は非同期に値を生成するシーケンス。for await で消費する。AsyncStream は手動でコールバックベース API を AsyncSequence に変換するラッパー。
// AsyncStream でイベントを非同期シーケンスにラップ
let ticks = AsyncStream<Int> { cont in
Task {
for i in 0..<5 {
try? await Task.sleep(nanoseconds: 100_000_000)
cont.yield(i)
}
cont.finish()
}
}
// for await で消費
for await tick in ticks {
print("tick: \(tick)")
}TaskLocal はタスクスコープで値を引き継ぐスレッドローカル的な仕組み。withTaskCancellationHandler でキャンセル時のクリーンアップをコールバックで登録できる。
// TaskLocal
enum RequestID {
@TaskLocal static var current: String = "none"
}
Task {
await RequestID.$current.withValue("req-123") {
print(RequestID.current) // req-123
await someWork()
}
}
// withTaskCancellationHandler
func fetchWithCancel() async throws -> Data {
let (data, _) = try await withTaskCancellationHandler {
try await URLSession.shared.data(from: someURL)
} onCancel: {
// キャンセル時のクリーンアップ
}
return data
}マクロ・Result Builders
応用@resultBuilder はブロック内の式を一つの値に変換する DSL 構築機能。SwiftUI の @ViewBuilder や some View の仕組みもこれで実装されている。
@resultBuilder
struct HTMLBuilder {
static func buildBlock(_ parts: String...) -> String {
parts.joined(separator: "\n")
}
static func buildIf(_ part: String?) -> String { part ?? "" }
}
func html(@HTMLBuilder content: () -> String) -> String {
"<html>\n\(content())\n</html>"
}
let page = html {
"<head><title>Test</title></head>"
"<body>Hello</body>"
}
print(page)Swift マクロはコンパイル時にコードを変換・生成する仕組み。@freestanding は式・宣言を生成し、@attached は既存の宣言にコードを追加する。
// @freestanding(expression) マクロの使用例
// (標準ライブラリの #stringify マクロ相当)
let (value, str) = #stringify(2 + 3)
// value = 5, str = "2 + 3"
// @attached(member) マクロの使用例
// Observation フレームワーク(Swift 5.9+)
import Observation
@Observable
class Store {
var count = 0 // @Observable が監視コードを自動生成
var name = ""
}@freestanding(expression) マクロはコンパイラプラグインとして実装する。SwiftSyntax で AST を操作し、新しいコードを生成して呼び出し箇所に展開する。
// マクロの宣言(パッケージ側)
@freestanding(expression)
public macro stringify<T>(_ value: T) -> (T, String) =
#externalMacro(module: "MyMacros", type: "StringifyMacro")
// マクロ実装(コンパイラプラグイン側)
// import SwiftSyntaxMacros
// public struct StringifyMacro: ExpressionMacro {
// public static func expansion(...) throws -> ExprSyntax {
// return "((node.arguments), \"(node.arguments)\")"
// }
// }Swift Package Manager・モジュール
応用Swift Package Manager (SPM) のマニフェストファイル。ターゲット・プロダクト・外部依存をコードで宣言する。swift build・swift test で操作する。
// Package.swift
import PackageDescription
let package = Package(
name: "MyLib",
platforms: [.macOS(.v13), .iOS(.v16)],
products: [
.library(name: "MyLib", targets: ["MyLib"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-algorithms", from: "1.0.0"),
],
targets: [
.target(
name: "MyLib",
dependencies: [.product(name: "Algorithms", package: "swift-algorithms")]
),
.testTarget(name: "MyLibTests", dependencies: ["MyLib"]),
]
)target はビルドの単位(ソースファイルの集合)。product は外部に公開する成果物(ライブラリや実行ファイル)。dependency で外部パッケージを取得する。
// 複数ターゲットの構成例
targets: [
// ライブラリターゲット
.target(name: "Core", path: "Sources/Core"),
// 実行ファイルターゲット(@main が必要)
.executableTarget(
name: "CLI",
dependencies: ["Core"],
path: "Sources/CLI"
),
// テストターゲット
.testTarget(
name: "CoreTests",
dependencies: ["Core"],
path: "Tests/CoreTests"
),
]Swift には 5 段階のアクセス制御がある。モジュール(フレームワーク/ライブラリ)の外に公開するには public、サブクラス化も許可するには open を使う。
// open: モジュール外からサブクラス化・オーバーライド可
// public: モジュール外からアクセス可(サブクラス化不可)
// internal: 同一モジュール内(デフォルト)
// fileprivate: 同一ファイル内
// private: 同一宣言スコープ内
open class Animal {
open func speak() { print("...") } // 外部でオーバーライド可
public func breathe() { print("呼吸") } // 外部からアクセス可
internal var energy = 100 // 同モジュール内
private var dna: String = "ATCG" // クラス内のみ
}@testable import ModuleName でテストターゲットからモジュールの internal メンバにアクセスできる。テスト用に公開レベルを上げる必要がなくなる。
// Tests/MyLibTests/MyLibTests.swift
import XCTest
@testable import MyLib // internal メンバにアクセス可能
final class MyLibTests: XCTestCase {
func testInternalLogic() throws {
// internal 関数を直接テストできる
let result = internalHelper(42)
XCTAssertEqual(result, 84)
}
}