本ツールで使用しているオープンソースライブラリ

本ツールのコードには 1 個のオープンソースライブラリが含まれています。

Swift 速習 — 簡潔なリファレンス

Swift 5.9 の構文、値型、プロトコルと最もよく使う標準ライブラリの速習ハンドブック。日常的な場面の約 80% をカバー。

Sw

Swift Swift 5.9

LLVM (swiftc / Swift toolchain) · マルチパラダイム(プロトコル指向 · OO · 関数型) · 静的 · 強い型付け

おすすめの学習パス

まず print と変数の束縛を覚える → 値型(struct/enum)とオプショナル Optional を身につける → 関数、クロージャ、コレクションを掘り下げる → プロトコル指向の設計と拡張を理解する → do-catch と throws でエラーを処理する → async/await と DispatchQueue で並行処理を書く → その後は必要に応じて URLSession、日付、ビルドとテストを学ぶ。FAQ の節は落とし穴を避けるために後から読み返すとよい。

1.Hello World とビルド環境

Swift プログラムの実行、SwiftPM プロジェクトとツールチェーンについて。

最小プログラム

トップレベルコードがプログラムのエントリポイントになる。print で出力する。import でモジュールを取り込む。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 新規ファイル main.swift:
import Foundation
print("Hello, world!")
// 実行:
// swift main.swift
// コンパイルして実行:
// swiftc main.swift -o app
// ./app
// ポイント:
// トップレベル文は直接実行される
// main 関数は不要(スクリプトモード)
// print は自動で改行
// import Foundation はよく使う

実行とビルド

swift はスクリプトを直接実行する。swiftc でコンパイルする。swift run でパッケージを実行する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// スクリプトを直接実行:
// swift script.swift
// 実行ファイルにコンパイル:
// swiftc main.swift -o app
// ./app
// SwiftPM パッケージを実行:
// swift run
// 実行ターゲットを指定:
// swift run MyTool
// 最適化コンパイル:
// swiftc -O main.swift
// 全ソースファイルをコンパイル:
// swiftc *.swift
// デバッグ情報:
// swiftc -g main.swift
// バージョン確認:
// swift --version

SwiftPM プロジェクト

swift package init で初期化する。Package.swift がマニフェスト。Sources がソースディレクトリ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 実行可能パッケージを作成:
// swift package init --type executable
// ライブラリパッケージを作成:
// swift package init --type library
// 構成:
// Package.swift マニフェスト
// Sources/ ソースコード
// MyTool/ ターゲットディレクトリ
// Tests/ テスト
// Package.swift:
// swift-tools-version:5.9
// import PackageDescription
// let package = Package(
// name: "MyTool",
// targets: [.executableTarget(
// name: "MyTool")]
// )
// ビルド:swift build
// 実行:swift run
// テスト:swift test

import による取り込み

import でモジュールを取り込む。Foundation はよく使われる。必要に応じてサブモジュールをインポートする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 標準モジュールをインポート:
import Foundation
import UIKit // iOS のUI
import SwiftUI // SwiftUI のUI
// Foundation が提供:
// 文字列、日付、ファイル、JSON
// 数値、コレクションの拡張
// サブモジュール:
import Foundation.NSURL
// プラットフォーム条件付きインポート:
#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif
// 自作モジュールのライブラリをインポート:
// import MyLibrary
// import 文はファイルの先頭に置く

出力と補間

print/print(items:)。文字列補間は \(値)。区切り文字と終端文字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let name = "Nick"
let age = 30
// 文字列補間:
print("\(name) 今年 \(age) 岁")
// 複数値の出力:
print(1, 2, 3) // スペース区切り
print("a", "b", separator: "-") // a-b
print("x", terminator: "") // 改行しない
// デバッグ出力:
debugPrint(name)
// 書式制御:
let pi = 3.14159
print(String(format: "%.2f", pi)) // 3.14
// 配列・辞書をそのまま出力:
print([1, 2, 3])
print(["a": 1])
// 自作型は CustomStringConvertible を実装

コマンドライン引数

CommandLine.arguments で引数を取得する。最初はプログラムのパス。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import Foundation
// 全引数:
let args = CommandLine.arguments
print("共 \(args.count) 个参数")
for (i, a) in args.enumerated() {
print("\(i): \(a)")
}
// 業務引数:
// args[0] はプログラムのパス
// 引数は args[1] から
if args.count > 1 {
let name = args[1]
print("你好 \(name)")
}
// 簡単なパース:
// 複雑な引数は ArgumentParser を使う
import ArgumentParser // 依存が必要
// 終了:
// exit(0) / exit(1)

複数ファイル

同じパッケージ内の複数のソースファイルが型を共有する。internal アクセスレベル。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Sources/App/main.swift
let greeter = Greeter(name: "Swift")
greeter.greet()
// Sources/App/Greeter.swift
// 同一パッケージ内の型は自動的に見える:
struct Greeter {
let name: String
func greet() {
print("你好,\(name)!")
}
}
// アクセスレベル:
// private ファイル内
// internal モジュール内(デフォルト)
// public モジュール外
// コンパイル:
// swiftc main.swift Greeter.swift
// SwiftPM は Sources/ を自動的に含める
// ファイル名は無関係、型名が一意ならよい

Xcode 統合

Xcode プロジェクトの構成。target と scheme。iOS 開発のワークフロー。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// プロジェクト構成:
// MyApp.xcodeproj
// Sources/
// Resources/
// Tests/
// target:ビルド単位
// iOS App / watchOS App
// Framework / Unit Test
// scheme:ビルド+実行の組み合わせ
// コマンドラインでビルド:
// xcodebuild -scheme MyApp build
// テストを実行:
// xcodebuild test -scheme MyApp
// シミュレータ:
// xcrun simctl list devices
// IPA を書き出し:
// xcodebuild archive
// SwiftPM パッケージはそのままドラッグ可能
// SwiftPM との違い:
// Xcode は署名とデバイスを処理

2.変数と定数

var/let バインディング、型推論、スコープ、命名について。

var と let

let の定数は不変。var の変数は可変。let の使用を優先する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定数:
let maxCount = 100
// maxCount = 200 // エラー:変更不可
// 変数:
var score = 0
score = 10 // 変更可能
// 型推論:
let name = "Swift" // String
let number = 42 // Int
// 明示的な型:
let count: Int = 5
var price: Double = 3.5
// 命名原則:
// let で済むなら let
// 変更が必要なら var
// 並行環境では不変性が安全
// コレクションは var で可変、let で読み取り専用

型推論

コンパイラがリテラルと文脈から型を推論する。複雑なケースでは明示的に注釈する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// リテラルから推論:
let a = 42 // Int
let b = 3.14 // Double
let c = "hi" // String
let d = true // Bool
// 文脈から推論:
var array: [Int] = []
array.append(1) // 要素は Int と推論
// リテラルは複数の型にできる:
let i: Int = 42
let f: Double = 42 // 可能
let u: UInt8 = 42
// 明示注釈が必要な場面:
// ジェネリクス、オプショナル、複雑な式
// コンパイラのエラー時に注釈を補う
// 型エイリアス:
typealias Age = Int
let age: Age = 30
// Option+Click で型を確認できる

明示的な型

コロンの後に型を注釈する。可読性が向上する。変換とリテラルの互換性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 基本的な注釈:
let name: String = "Swift"
let count: Int = 10
let ratio: Double = 0.5
// オプショナルの注釈:
var optional: String? = nil
// コレクションの注釈:
var list: [Int] = [1, 2]
var dict: [String: Int] = [:]
// タプルの注釈:
var pair: (Int, String) = (1, "a")
// 関数型の注釈:
var handler: (Int) -> Void
// 必要な場面:
// 空コレクションは推論できない
// プロトコル型、ジェネリクス
// 公開 API は注釈必須
// ドキュメントとコンパイル時チェックに役立つ

定数

let で定数を宣言する。参照型の定数は不変の参照。コンパイル時定数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 基本的な定数:
let days = 7
// 定数の配列・辞書:
let fixed = [1, 2, 3]
// fixed.append(4) // エラー:変更不可
// 定数の参照型:
class Counter { var n = 0 }
let c = Counter()
// c は変更不可だが、オブジェクトのプロパティは可変:
c.n += 1 // 可能
// グローバル定数:
let appName = "GuruToolkit"
// コンパイル時定数:
// static let / enum の名前空間
enum Config {
static let maxRetry = 3
}
print(Config.maxRetry)
// 定数は計算可能:
let total = days * 24

シャドーイング

内側のスコープで同名の変数が外側を覆い隠す。if/switch の局所定数が一般的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let x = 10
// 内側で遮蔽:
if true {
let x = 20 // 外側を遮蔽
print(x) // 20
}
print(x) // 10
// よくあるパターン:
// オプショナルバインディングと同名:
var value: Int? = 5
if let value = value {
print(value) // 新しい定数 5
}
// ループ変数の遮蔽:
for x in 1...3 {
print(x) // 1 2 3
}
// 注意:
// 遮蔽は可読性を下げる
// 複雑な関数では多層の遮蔽を避ける
// オプショナルバインディングは遮蔽で安全にアンラップ

スコープ

{} がスコープを定義する。内側から外側にはアクセスできる。外側から内側にはアクセスできない。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let outer = 1
// ブロックスコープ:
{
let inner = 2
print(outer) // 内側から外側にアクセス可
print(inner)
}
// print(inner) // エラー:スコープ外
// 関数スコープ:
func demo() {
let local = "函数内"
print(local)
}
// ループスコープ:
for i in 1...3 {
let sq = i * i
print(sq)
}
// 同名の規則:
// 同じスコープで再宣言できない
// ネストしたスコープで遮蔽できる
// ライフサイクル:
// スコープが終わると変数は破棄
// グローバル変数はプログラムのライフサイクル全体で存在

命名規則

Swift の命名規約。camelCase、明確な動詞、略語の回避。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 変数・定数:camelCase
let userName = "nick"
var totalCount = 0
// 型:CamelCase
struct UserAccount {}
class NetworkManager {}
enum ColorChoice {}
// プロトコル:動詞・形容詞で命名
protocol CanGreet {}
protocol Named {}
// ブールのプロパティ:is/has のプレフィックス
var isEnabled = true
var hasPermission = false
// メソッド:動詞で始める
func saveData() {}
func loadConfig() {}
// 公式の API 設計規約:
// 名前は文章のように読める
// 略語と曖昧さを避ける
// ドキュメントコメント /// で用途を説明

型変換

数値型は明示的に変換する。init() イニシャライザ。文字列と数値の相互変換。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 数値変換(明示的):
let i = 42
let d = Double(i) // 42.0
let u = UInt8(i) // 42
// 暗黙の変換は行われない:
// let x = 1 + 2.0 // エラー
let x = Double(1) + 2.0 // 正しい
// 文字列から数値へ:
let s = "42"
let n = Int(s) // Int? オプショナル
let f = Double("3.14") // Double?
// 数値から文字列へ:
let str = String(42)
let str2 = "\(3.14)"
// オプショナルのアンラップ:
if let n = Int(s) {
print(n)
}
// 切り捨て:Int(3.9) // 3

3.型システム

基本型、タプル、構造体、列挙型、オプショナルについて。

基本型

Int、Double、Bool、String、Character。値型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 整数:
let i: Int = -10
let u: UInt = 10
// 浮動小数点:
let f: Float = 1.5 // 32 ビット
let d: Double = 3.14159 // 64 ビット
// ブール:
let t: Bool = true
let f2: Bool = false
// 文字列と文字:
let s: String = "hello"
let c: Character = "A"
// 型エイリアス:
let whole: Int = 42
// 整数の範囲:
print(Int.min, Int.max)
// 標準ライブラリの型:
// Int/Double は値型
// 代入はコピーであって参照ではない
// メソッド呼び出し:i.description

整数型

Int8〜Int64、UInt 系。プラットフォームネイティブの Int。オーバーフロー処理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 符号付き:Int8 Int16 Int32 Int64
// 符号なし:UInt8 UInt16 UInt32 UInt64
// プラットフォームのデフォルト:Int(64 ビット)
let a: Int8 = -128
let b: UInt8 = 255
let c: Int64 = 9_223_372_036_854_775_807
// アンダースコア区切り:
let big = 1_000_000
// オーバーフロー:
// デフォルトではエラー(コンパイル時のリテラル)
// 演算のオーバーフローにはオーバーフロー演算子:
var x: UInt8 = 250
x = x &+ 10 // ラップして 4
// &- &* &/ オーバーフロー演算
// オーバーフローの検査:
// x.addingReportingOverflow(10)
// 優先順位が明確な時は型注釈

浮動小数点数

Float/Double の精度。CGFloat は UI 座標。演算と特殊な値。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Double 64 ビット / Float 32 ビット
let d = 3.141592653589793
let f: Float = 3.14
// 科学的記数法:
let e = 1.5e3 // 1500.0
// リテラルは Double と推論
// 演算:
let sum = d + 1.0
let sqrt = d.squareRoot()
let pow = pow(2.0, 10.0)
// 特殊な値:
let nan = Double.nan
let inf = Double.infinity
nan.isNaN // true
inf.isInfinite // true
// 比較:
// 浮動小数点の等価比較は誤差を許容する必要がある
let a = 0.1 + 0.2 // 0.30000000000000004
// 書式化:
String(format: "%.2f", a)
// 整数化:
Int(3.7) // 3
3.7.rounded() // 4.0

タプル

複数の複合値。名前付き要素。分解。軽量なデータのグループ化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 基本タプル:
let pair = (1, "one")
print(pair.0) // 1
print(pair.1) // "one"
// 名前付き要素:
let user = (name: "Nick", age: 30)
print(user.name)
print(user.age)
// 分解:
let (code, message) = (200, "OK")
print(code)
// 一部を無視:
let (x, _) = (1, 2)
// 型注釈:
let point: (x: Double, y: Double) = (0, 0)
// 関数で複数の値を返す:
func div(a: Int, b: Int) -> (q: Int, r: Int) {
(a / b, a % b)
}
let result = div(a: 10, b: 3)
print(result.q, result.r)
// 辞書のループでタプルを分解

構造体

struct は値型。自動メンバーイニシャライザ。プロパティとメソッド。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 構造体を定義:
struct Point {
var x: Double
var y: Double
// メソッド:
func distance() -> Double {
(x * x + y * y).squareRoot()
}
// 可変メソッド:
mutating func moveBy(dx: Double) {
x += dx
}
}
// 自動イニシャライザ:
let p = Point(x: 1, y: 2)
print(p.distance())
// 値型のコピー:
var p2 = p
p2.x = 100
print(p.x) // 1、p は影響を受けない
// 計算プロパティは OOP の節を参照
// class より struct を優先

列挙型

enum で関連する値をグループ化する。関連値。生の値。網羅的マッチング。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 単純な列挙型:
enum Direction {
case north, south, east, west
}
// 関連値:
enum Result2 {
case success(Int)
case failure(String)
}
// 生の値:
enum Color: String {
case red = "#FF0000"
case green = "#00FF00"
}
// 使用:
let dir = Direction.north
switch dir {
case .north: print("北")
case .south: print("南")
case .east: print("东")
case .west: print("西")
}
// 関連値の取得:
let r = Result2.success(200)
if case .success(let v) = r {
print(v)
}
// 生の値:
print(Color.red.rawValue)
// 列挙型にメソッドを追加できる

オプショナル

Optional は空の可能性を表す。T? 構文。nil は値なしを表す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// オプショナルを宣言:
var name: String? = nil
var age: Int? = 30
// 代入:
name = "Nick"
// アンラップの方法:
// 1. 強制アンラップ(危険):
// print(name!)
// 2. オプショナルバインディング:
if let n = name {
print(n)
}
// 3. nil合体演算子:
let display = name ?? "未知"
// 4. オプショナルチェーン:
// name?.count
// nil かどうかの判定:
if name == nil { print("无名字") }
// 実際の型:
// Optional<String> は列挙型
// .some と .none を持つ

ネスト型

型の内部に型を定義する。名前空間で整理する。列挙型は関連する型をまとめられる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ネストした型:
struct Player {
// 構造体内に列挙型をネスト:
enum State {
case idle, running, dead
}
var state: State = .idle
// 型エイリアスのネスト:
typealias Health = Int
var health: Health = 100
}
// 使用:
let p = Player()
let s: Player.State = .running
print(s)
// ネストした型にアクセス:
// Player.State
// ネストして分類整理:
enum HTTP {
enum Status {
static let ok = 200
static let notFound = 404
}
}
print(HTTP.Status.ok)
// 利点:
// 関連する型を近くに定義
// トップレベルの名前の衝突を減らす
// 「何に属するか」の関係を表現
// 深いネストは適度に

4.値型と参照型

値セマンティクス、クラスの参照、コピーオンライト、メモリレイアウト。

値型 vs 参照型

struct は値セマンティクスでコピーされる。class は参照を共有する。言語の中核的な違い。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 値型:
struct Point { var x = 0 }
var a = Point()
a.x = 10
var b = a // コピー
b.x = 99
print(a.x) // 10、独立
// 参照型:
class Box { var value = 0 }
let c = Box()
c.value = 5
let d = c // 参照を共有
d.value = 100
print(c.value) // 100、同じオブジェクト
// 選択の原則:
// 値型をデフォルトで優先
// 共有・継承が必要なら class
// 等価比較の意味論が必要なら値型
// Swift では struct が中心

class のセマンティクス

class は参照型。アイデンティティは一意。可変性とスレッド共有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// クラス定義:
class Counter {
var count = 0
}
// 参照の共有:
let c1 = Counter()
let c2 = c1
c2.count += 1
print(c1.count) // 1、同じオブジェクト
// 同一性の比較:
// === は参照の同一性を比較
if c1 === c2 { print("同一实例") }
// == は値を比較(Equatable が必要)
// 参照型の可変性:
// 定数参照でもプロパティは変更可能
// マルチスレッドで共有する場合は同期に注意
// クラスは継承をサポート(OOP の節)
// クラスには deinit がある

コピーオンライト

配列などの値型は内部でストレージを共有する。変更が加わったとき初めて実際にコピーされる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 配列:
var a = [1, 2, 3]
var b = a // 内部ストレージを共有
print(a) // [1, 2, 3]
// 変更時にコピー:
b.append(4)
print(a) // [1, 2, 3] のまま
// コピーオンライトの最適化:
// 代入はほぼゼロコスト
// 最初の変更でのみコピーを確保
// 大きな配列の複数代入も効率的
// 自作型でも最適化可能:
// isKnownUniquelyReferenced を使う
// 参照の一意性を確認
// 文字列・辞書も同様に最適化
// 値の意味論と高性能を両立

同一性と等価性

=== は参照の同一性。== は値の等価性 (Equatable)。その違いが重要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 同一性 ===:
class Person { var name: String
init(name: String) { self.name = name }
}
let p1 = Person(name: "Nick")
let p2 = p1
let p3 = Person(name: "Nick")
// 同一性:
print(p1 === p2) // true 同じオブジェクト
print(p1 === p3) // false 別のオブジェクト
// 値の等価(Equatable が必要):
struct Point: Equatable {
var x: Int, y: Int
}
let q1 = Point(x: 1, y: 2)
let q2 = Point(x: 1, y: 2)
print(q1 == q2) // true 値が同じ
// 規則:
// 参照型は === で同一性を判定
// 値型は == で等価を判定
// Equatable を自前で実装
// ハッシュと等価は一致させる(Hashable)

weak と unowned

weak は弱参照でカウントを増やさない。unowned は非所有参照。循環参照を断ち切る。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
var pet: Pet?
deinit { print("Person 释放") }
}
class Pet {
// weak は保持しない:
weak var owner: Person?
deinit { print("Pet 释放") }
}
var nick: Person? = Person()
var dog: Pet? = Pet()
nick?.pet = dog
dog?.owner = nick
// 循環参照は解消された:
nick = nil // 解放
print("nick 已释放")
// weak は var・オプショナル必須
// unowned:常に値があると想定
// class Restaurant {
// unowned var chef: Chef
// }
// 解放済み unowned へのアクセスはクラッシュ
// weak を優先

inout パラメータ

inout パラメータは参照渡し。関数内で元の値を変更できる。& を付けて呼び出す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// inout 関数:
func swapValues(_ a: inout Int, _ b: inout Int) {
let tmp = a
a = b
b = tmp
}
// 呼び出し:
var x = 1
var y = 2
swapValues(&x, &y)
print(x, y) // 2 1
// 配列の要素を変更:
func increment(_ n: inout Int) {
n += 1
}
var nums = [10, 20]
increment(&nums[0])
print(nums) // [11, 20]
// 制限:
// リテラル・定数は渡せない
// 同じ変数を同時に 2 回渡せない
// プロパティは var で可変であること
// 参照型の引数との違い:
// inout は値を出入りさせるための経路

ポインタの相互運用

UnsafePointer と C の相互運用。メモリ管理は自己責任。危険な領域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 変数のポインタを取得:
var number = 42
// withUnsafePointer のスコープ:
withUnsafePointer(to: &number) { ptr in
print(ptr.pointee) // 42
}
// 可変ポインタ:
withUnsafeMutablePointer(to: &number) { ptr in
ptr.pointee = 100
}
print(number) // 100
// C 関数との相互運用:
// 配列を C のポインタに渡す:
var arr = [1, 2, 3]
arr.withUnsafeBufferPointer { buf in
// buf.baseAddress を C に渡す
}
// 注意:
// ポインタのライフサイクルは手動で保証する
// スコープ外にポインタを保存しない
// ほとんどの場面では避ける
// 性能のボトルネックで検討

メモリレイアウト

型のメモリレイアウト。バイトアラインメント。性能と相互運用に影響する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Foundation
// 型のサイズを確認:
print(MemoryLayout<Int>.size) // 8
print(MemoryLayout<Double>.size) // 8
print(MemoryLayout<Bool>.size) // 1
// アライメント:
print(MemoryLayout<Int>.alignment)
// 構造体のレイアウト:
struct Small {
let a: Int8 // 1 バイト
let b: Int64 // 8 バイト
}
print(MemoryLayout<Small>.size) // パディング含む
// コンパクトなレイアウト:
// フィールドの順序がパディングに影響
// 大きいフィールドを前におくとコンパクト
// 最適化:
// 大量の小さな構造体を避ける
// 配列は連続ストレージ
// C との相互運用ではレイアウトが重要
// 手動最適化は滅多に必要ない

5.制御フロー

if、switch、guard、ループ、オプショナルバインディングについて。

if / else

if で条件付き実行。else if で複数分岐。条件に括弧は不要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let score = 85
// 基本の if:
if score >= 60 {
print("及格")
}
// else if:
if score >= 90 {
print("优秀")
} else if score >= 60 {
print("及格")
} else {
print("不及格")
}
// 条件は Bool 必須:
// if score { } // エラー
// 複数条件:
if score >= 60 && score < 90 {
print("良好")
}
// 1 行の if:
if score > 80 { print("高分") }
// 三項演算子:
let label = score >= 60 ? "过" : "不过"

switch

switch は網羅的マッチング。範囲、タプル、バインディングに対応。break は不要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let value = 5
// 値のマッチング:
switch value {
case 1, 2, 3:
print("小")
case 4...6:
print("中")
default:
print("大")
}
// 範囲と where:
let temp = 25
switch temp {
case 0...10: print("冷")
case 11...30 where temp >= 20: print("暖")
case 11...30: print("温")
default: print("热")
}
// 列挙型の網羅:
enum Grade { case a, b, c }
let g = Grade.a
switch g {
case .a: print("优秀")
case .b: print("良好")
case .c: print("及格")
}
// タプルのマッチング:
// switch (x, y) { case (0, 0): ... }

guard

guard で早期退出する。else ブロックはスコープを退出する必要がある。else を持つ分岐として書く。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// guard で早期リターン:
func validate(_ age: Int?) {
guard let age = age else {
print("年龄为空")
return
}
// ここで age はアンラップ済み
guard age >= 18 else {
print("未成年")
return
}
print("成人,年龄 \(age)")
}
validate(nil)
validate(15)
validate(20)
// if let との違い:
// guard のアンラップ値は後でも使える
// if let は分岐内でのみ有効
// guard の else は必ず退出する:
// return/throw/break/continue
// 構造が明確でインデントが浅い
// メインの処理ロジックをトップレベルに保つ

for-in ループ

for-in で範囲・配列・辞書を反復処理する。インデックス付きで反復する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 範囲:
for i in 1...5 { // 5 を含む
print(i)
}
for i in 1..<5 { // 5 を含まない
print(i)
}
// 配列:
let names = ["a", "b", "c"]
for name in names {
print(name)
}
// インデックス付き:
for (i, name) in names.enumerated() {
print("\(i): \(name)")
}
// 辞書:
let scores = ["a": 90, "b": 80]
for (key, value) in scores {
print("\(key): \(value)")
}
// 逆順:
for i in (1...3).reversed() {}
// ステップ:
for i in stride(from: 0, to: 10, by: 2) {}
// 配列のインデックス:
for i in names.indices { print(names[i]) }

while ループ

while は条件ループ。repeat-while は実行後に判定する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var n = 3
// while:
while n > 0 {
print(n)
n -= 1
}
// repeat-while(最低 1 回は実行):
var count = 0
repeat {
count += 1
print("第 \(count) 次")
} while count < 3
// ループを抜ける:
var found = false
var i = 0
while !found && i < 10 {
i += 1
if i == 5 { found = true }
}
// 用途:
// 条件が不明なループ
// ポーリング、リトライのロジック
// for-in との違い:
// 回数が分からない時は while
// repeat-while はメニューの対話に適する

break と continue

break はループを抜ける。continue はこの周回をスキップする。ラベル付きでネストしたループを抜けられる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// continue:
for i in 1...10 {
if i % 2 == 0 {
continue // 偶数をスキップ
}
print(i) // 1 3 5 7 9
}
// break:
for i in 1...10 {
if i > 5 {
break // 途中で抜ける
}
print(i)
}
// ラベルでネストから抜ける:
outer: for i in 1...3 {
for j in 1...3 {
if i * j == 6 {
print("命中 \(i) \(j)")
break outer // 全てを抜ける
}
}
}
// ラベルの名前は任意
// 複雑なネストの制御フローで使う
// 適度に使う、過度は可読性を下げる

if let

オプショナルバインディングでアンラップ。guard let で早期脱出。複数値を同時に展開。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var name: String? = "Nick"
var age: Int? = 30
// 単一のアンラップ:
if let name = name {
print("名字 \(name)")
}
// 複数のアンラップ(カンマ区切り):
if let name = name, let age = age {
print("\(name) \(age) 岁")
}
// 条件付き:
if let name = name, name.count > 2 {
print(name)
}
// while 内でのアンラップ:
var nums = [1, 2, 3]
while let n = nums.popLast() {
print(n) // 3 2 1
}
// 値を気にせず nil 判定:
if name != nil { print("有值") }
// 注意:
// if let は分岐の外では使えない
// 後でも使うなら guard let

パターンマッチング

switch/if でのパターン。case let、where。型マッチング。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 関連値のバインド:
enum Result3 {
case success(Int)
case failure(String)
}
let r = Result3.success(200)
switch r {
case .success(let code):
print("成功 \(code)")
case .failure(let msg):
print("失败 \(msg)")
}
// 範囲マッチング:
let grade = 85
switch grade {
case 90...100: print("A")
case 80..<90: print("B")
case 70..<80: print("C")
default: print("D")
}
// 型マッチング:
let any: Any = 42
switch any {
case let i as Int: print("整数 \(i)")
case let s as String: print("字符串 \(s)")
default: print("其他")
}
// where 条件:
switch grade {
case let g where g % 5 == 0: print("整五分")
default: print("其他分")
}

6.関数

関数の定義、引数ラベル、戻り値、関数型とクロージャ。

関数定義

func で関数を定義。引数と戻り値。呼び出し構文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 基本関数:
func greet(name: String) -> String {
"你好,\(name)!"
}
// 戻り値なし:
func sayHello() {
print("hello")
}
// 戻り値なしは Void と明記も可:
func log(_ msg: String) -> Void {}
// 呼び出し:
let g = greet(name: "Nick")
print(g)
sayHello()
// 複数引数:
func add(a: Int, b: Int) -> Int {
a + b
}
// 引数のデフォルトラベル=引数名
// 呼び出し時はラベルが必要
// 最後の式が戻り値になる

引数ラベル

引数ラベルと内部名。_ でラベルを省略。可読性の設計。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// カスタムラベル:
func move(from start: Int, to end: Int) {
print("从 \(start)\(end)")
}
move(from: 1, to: 10)
// ラベルを省略:
func sum(_ a: Int, _ b: Int) -> Int {
a + b
}
print(sum(1, 2))
// 混在:
func configure(_ name: String, debug: Bool = false) {
print(name, debug)
}
configure("app") // ラベルを省略
configure("app", debug: true)
// API 設計規約:
// ラベルで呼び出しが文のように読める
// 前置詞 with/for/in が一般的
// ラベルは明確な言葉で曖昧さを避ける

デフォルト引数値

引数のデフォルト値。呼び出し時に省略可能。末尾、またはすべてにデフォルトが必要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// デフォルト値:
func greet2(_ name: String, times: Int = 1) {
for _ in 1...times {
print("你好 \(name)")
}
}
greet2("Nick") // デフォルトの 1 を使用
greet2("Nick", times: 3)
// デフォルト値のよくある使い方:
func fetch(
url: String,
timeout: Double = 30,
retries: Int = 3
) {
print(url, timeout, retries)
}
// 任意の組み合わせで呼び出し:
fetch(url: "https://x")
fetch(url: "https://x", retries: 5)
// 規則:
// デフォルト値は引数リストの最後
// デフォルト引数は省略可能
// デフォルト値は関数シグネチャに反映される
// 可読性と柔軟性のバランス

戻り値

戻り値の型。複数値はタプルで。単一式は暗黙的に return。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 単一の戻り値:
func square(_ n: Int) -> Int {
n * n
}
// タプルで複数の値を返す:
func stats(_ nums: [Int]) -> (min: Int, max: Int, sum: Int) {
(nums.min() ?? 0, nums.max() ?? 0, nums.reduce(0, +))
}
let s = stats([3, 1, 4, 1])
print(s.min, s.max, s.sum)
// オプショナルを返す:
func findIndex(of value: Int, in arr: [Int]) -> Int? {
arr.firstIndex(of: value)
}
if let i = findIndex(of: 4, in: [1, 2, 4]) {
print(i) // 2
}
// 戻り値なし:
func noReturn() {}
// 暗黙的な return:
// 単一式は return を省略できる
// 複数文では明示的な return が必要

可変長引数

... で複数の引数を配列に集約。任意の個数。最後の引数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 可変長引数:
func average(_ numbers: Double...) -> Double {
let sum = numbers.reduce(0, +)
return numbers.isEmpty ? 0 : sum / Double(numbers.count)
}
print(average(1, 2, 3, 4)) // 2.5
print(average()) // 0
// 可変長引数は配列として扱う
// 任意の個数を出力:
func logAll(_ items: String...) {
for item in items {
print(item)
}
}
logAll("a", "b", "c")
// 規則:
// 可変長引数は最後に置く
// 1 つの関数に最大 1 つ
// 内部では [T] の配列
// 呼び出し時はカンマ区切り

inout パラメータ

inout で外部変数を変更。& で参照渡し。値型のインプレース変更。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 交換:
func swapTwo<T>(_ a: inout T, _ b: inout T) {
let tmp = a
a = b
b = tmp
}
var x = 5
var y = 10
swapTwo(&x, &y)
print(x, y) // 10 5
// 値型の変更:
func scale(_ v: inout Double, by factor: Double) {
v *= factor
}
var price = 100.0
scale(&price, by: 0.8)
print(price) // 80.0
// 規則:
// 定数・リテラルではなく変数を渡す
// & プレフィックス
// 参照型との違い:
// inout は関数内外での値の往復
// 新しいインスタンスは作らない
// 大きな値型のコピー回避に適する

関数型

関数は型として扱える。代入、引数、戻り値。第一級オブジェクト。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 関数型の注釈:
func add(a: Int, b: Int) -> Int { a + b }
// (Int, Int) -> Int
// 代入:
let operation: (Int, Int) -> Int = add
print(operation(1, 2))
// 引数として渡す:
func apply(_ f: (Int) -> Int, to value: Int) -> Int {
f(value)
}
func double(_ n: Int) -> Int { n * 2 }
print(apply(double, to: 5)) // 10
// 関数を返す:
func makeAdder(_ base: Int) -> (Int) -> Int {
{ n in base + n }
}
let addTen = makeAdder(10)
print(addTen(5)) // 15
// オプショナルの関数型:
var handler: (() -> Void)?
// 呼び出し時はオプショナルを扱う
// 型マッチは厳密(ラベル含む)

クロージャ

クロージャはコンテキストをキャプチャ。末尾クロージャ。引数名の省略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// クロージャを定義:
let square2 = { (n: Int) -> Int in
n * n
}
print(square2(4))
// 変数のキャプチャ:
var counter = 0
let increment = {
counter += 1
}
increment()
increment()
print(counter) // 2
// トレーリングクロージャ:
func perform(_ action: () -> Void) {
action()
}
perform {
print("尾随闭包")
}
// ソートの簡略記法:
let nums = [3, 1, 2]
let sorted = nums.sorted { $0 < $1 }
print(sorted)
// 省略記法:
// $0 $1 は引数の省略
// キャプチャリスト [weak self]
// @escaping エスケープクロージャ

7.文字列

String の操作、補間、部分文字列、Unicode とフォーマット。

String の基礎

String は値型。リテラルと可変性。Character で構成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 文字列を作成:
let greeting = "Hello"
// 複数行文字列:三重引用符リテラル
// let multi = """
// 複数行の
// 文字列
// """
// 等価(\n 改行):
let multi = "多行\n字符串"
// 可変文字列:
var text = "start"
text += " end"
// 空文字列:
let empty = ""
let empty2 = String()
// 文字の並び:
// String は Character で構成
// 文字を走査:
for c in text {
print(c)
}
// カウント(文字数):
print(text.count)
// 空の判定:
text.isEmpty
// 文字列は値型:
// 代入はコピー
// 効率的(コピーオンライト)

補間

\(式) で値を埋め込み。型安全。任意の式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let name = "Nick"
let age = 30
let score = 95.5
// 基本的な補間:
let msg = "\(name) 今年 \(age) 岁"
print(msg)
// 式の補間:
let calc = "\(age * 2)\(age) 的两倍"
// メソッド呼び出し:
let upper = "大写: \(name.uppercased())"
// 値の書式化:
let formatted = "分数: \(String(format: "%.1f", score))"
// ネストした補間:
let nested = "\(name)\("\(age)".count) 个字符"
// バックスラッシュのエスケープ:
let backslash = "反斜杠:\\"
// 注意:
// 補間する型は表示可能であること
// 自作型は CustomStringConvertible を実装

連結

文字列の連結。append。join。+= 演算子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// + 連結:
let a = "hello"
let b = " world"
let c = a + b // 新しい文字列
// += 追記:
var s = "start"
s += " - more"
// append:
s.append("!")
// 文字の追記:
s.append(Character("?"))
// 配列の join:
let parts = ["a", "b", "c"]
let joined = parts.joined(separator: ", ")
print(joined) // "a, b, c"
// 性能:
// 小さな文字列の連結は低コスト
// 大量の連結:
// 配列 + joined を使う
// または可変文字列で一度に構築
// 文字列は値型、連結は新しい値を作る

複数行文字列

三重引用符での複数行。インデントの切り詰め。補間値。 区切り。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 複数行文字列は三重引用符リテラル:
// let poem = """
// 床前明月光,
// 疑是地上霜。
// """
// 等価な実装(\n 改行):
let poem = "床前明月光,\n疑是地上霜。"
print(poem)
// インデント規則:閉じ引用符のインデントが基準
// 引用符を含める:
// let quote = """
// He said "hi"
// """
// 複数行の補間:
let name = "Rust"
// let info = """
// 语言:\(name)
// 类型:静态
// """
// バックスラッシュで改行を打ち消し:
// let oneLine = """
// 一行内容 \
// 続き
// """

よく使うメソッド

大文字小文字変換、検索、置換、分割、トリミング。よく使う String API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let s = " Hello, Swift "
// 大文字小文字:
let lower = s.lowercased()
let upper = s.uppercased()
// トリム:
let trimmed = s.trimmingCharacters(
in: .whitespacesAndNewlines)
// 判定:
s.contains("Swift")
s.hasPrefix(" ")
s.hasSuffix(" ")
// 検索:
s.firstIndex(of: ",")
// 置換:
let replaced = s.replacingOccurrences(
of: "Swift", with: "Go")
// 分割:
let parts = "a,b,c".split(separator: ",")
// プレフィックス・サフィックス:
let pre = s.prefix(5)
// 空判定:s.isEmpty
// 繰り返し:
String(repeating: "ab", count: 3)

部分文字列

Substring は元の文字列を参照。スライスは Substring を返す。String 変換で所有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let full = "Hello, Swift World"
// インデックス範囲でスライス:
let start = full.index(full.startIndex, offsetBy: 7)
let end = full.index(start, offsetBy: 5)
let sub = full[start..<end] // "Swift"
print(sub)
// 注意:Substring 型
// 元の文字列とストレージを共有
// String に変換して独立に保持:
let owned = String(sub)
// プレフィックス・サフィックスは Substring:
let p = full.prefix(5) // "Hello"
// Range でスライス:
if let range = full.range(of: "Swift") {
print(full[range])
}
// マルチバイト安全:
// インデックスは文字の境界基準
// 整数の添字は使わない
// 頻繁に使うなら String に変換

Unicode

文字は Unicode スカラーで構成。コードポイント。拡張書記素クラスタ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Unicode リテラル:
let heart = "\u{2764}"
print(heart) // ❤
// 拡張書記素クラスタ:
let e = "\u{E9}" // é
let combo = "e\u{301}" // é(結合)
print(e == combo) // true、同じ書記素クラスタ
// 文字(書記素クラスタ)数:
let flag = "🇨🇳"
print(flag.count) // 1(地域インジケータの組合せ)
// 書記素クラスタの走査:
for c in "你好👍" {
print(c)
}
// コードポイント:
for scalar in "中".unicodeScalars {
print(scalar.value) // 20013
}
// 正規化:
// .precomposedStringWithCanonicalMapping()
// ソート比較には正規化が必要

フォーマット

String(format:) で C 風のフォーマット。数値のパディングと精度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Foundation
// 基本的な書式:
let pi = 3.14159
let f = String(format: "%.2f", pi)
print(f) // "3.14"
// 整数のゼロ埋め:
String(format: "%05d", 42) // "00042"
// 幅の整列:
String(format: "%10s", "hi") // 右寄せ
// 科学的記数法:
String(format: "%e", 1234.0)
// 16 進数:
String(format: "%x", 255) // "ff"
// 複数プレースホルダ:
String(format: "%d-%@", 2026, "08-02")
// 注意:
// %@ オブジェクト、%d 整数、%f 浮動小数点
// %lld 64 ビット整数
// より Swift らしい方法:
// 補間と description を優先
// 正確な書式が必要な時だけ format

8.コレクション

Array、Dictionary、Set、高階関数と範囲。

Array

順序付きの値のコレクション。ジェネリック [T]。追加、削除、変更、検索。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 作成:
var nums = [1, 2, 3]
let empty: [Int] = []
let filled = [Int](repeating: 0, count: 5)
// リテラルの型推論:
let names = ["a", "b"]
// アクセス:
print(nums[0])
print(nums.first ?? 0)
print(nums.last ?? 0)
// 追加・削除:
nums.append(4)
nums.insert(0, at: 0)
nums.removeLast()
nums.remove(at: 1)
// 走査:
for n in nums { print(n) }
// その他:
nums.count
nums.isEmpty
nums.contains(2)
// 合計:
let sum = nums.reduce(0, +)
// ソート:
nums.sorted()

配列の操作

スライス、置換、結合、検索。よく使う高階メソッド。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var arr = [1, 2, 3, 4, 5]
// スライス:
let slice = arr[1...3] // ArraySlice
print(slice)
// 範囲の置換:
arr.replaceSubrange(0..<2, with: [9, 9])
// 結合:
let combined = arr + [10]
// 検索:
arr.first { $0 > 3 }
arr.last { $0 > 3 }
arr.contains(5)
arr.firstIndex(of: 3)
// 一括変換:
let doubled = arr.map { $0 * 2 }
let evens = arr.filter { $0 % 2 == 0 }
// 分割:
let (small, big) = arr.partitioned {
$0 < 3
}
// プレフィックス・サフィックス:
arr.prefix(2)
arr.suffix(2)
// 交換:
arr.swapAt(0, 1)

Dictionary

キーと値のペアのコレクション。[String: T]。順序なし。O(1) 検索。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 作成:
var scores: [String: Int] = [:]
var config = ["env": "dev", "port": "8080"]
// 追加・変更:
scores["alice"] = 90
scores["bob"] = 85
scores["alice"] = 95 // 上書き
// 読み取り(オプショナル):
let alice = scores["alice"] // Int?
let missing = scores["x"] // nil
// デフォルト値:
let v = scores["x"] ?? 0
// 削除:
scores["bob"] = nil
scores.removeValue(forKey: "alice")
// 走査:
for (k, v) in scores {
print("\(k): \(v)")
}
// キー・値のリスト:
Array(scores.keys)
Array(scores.values)
// マージ:
config.merge(["port": "9090"]) { _, new in new }
// カウントと空判定:
scores.count

Set

順序なしの一意な要素のコレクション。ハッシュセット。集合演算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 作成:
var set: Set<Int> = [1, 2, 3]
let other: Set = [3, 4, 5]
// 追加・削除・検索:
set.insert(4)
set.remove(1)
set.contains(2)
// 集合演算:
let union = set.union(other) // 和集合
let intersect = set.intersection(other) // 積集合
let diff = set.subtracting(other) // 差集合
let sym = set.symmetricDifference(other) // 対称差
// 関係の判定:
set.isSubset(of: other)
set.isSuperset(of: other)
set.isDisjoint(with: other)
// 走査:
for v in set { print(v) }
// 配列に変換:
let arr = Array(set)
// 用途:
// 重複除去、存在チェック
// 重複除去:
let dup = Array(Set([1, 1, 2, 3]))
print(dup)

反復処理

配列/辞書/セットの反復処理。インデックス付き。逆順。条件付き反復。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let items = ["a", "b", "c"]
// 基本的な走査:
for item in items {
print(item)
}
// インデックス付き:
for (i, item) in items.enumerated() {
print("\(i): \(item)")
}
// 添字の走査:
for i in items.indices {
print(items[i])
}
// 逆順:
for item in items.reversed() {}
// 辞書の走査:
let dict = ["a": 1, "b": 2]
for (k, v) in dict {
print("\(k): \(v)")
}
// 条件でフィルタして走査:
for n in 1...10 where n % 2 == 0 {
print(n) // 2 4 6 8 10
}
// スキップと終了:
// continue / break
// forEach:
items.forEach { print($0) }

高階関数

map、filter、reduce、compactMap、sorted。関数型のパイプライン処理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let nums = [1, 2, 3, 4, 5]
// map マッピング:
let doubled = nums.map { $0 * 2 }
// filter フィルタ:
let evens = nums.filter { $0 % 2 == 0 }
// reduce 畳み込み:
let sum = nums.reduce(0, +)
// compactMap で nil 除去:
let strs = ["1", "a", "3"]
let ints = strs.compactMap { Int($0) }
print(ints) // [1, 3]
// flatMap で平坦化:
let grid = [[1, 2], [3]]
let flat = grid.flatMap { $0 }
// sorted ソート:
let sorted = nums.sorted { $0 > $1 }
// チェーンで組み合わせ:
let result = nums
.filter { $0 % 2 == 0 }
.map { $0 * $0 }
.reduce(0, +)
print(result) // 4 + 16 = 20
// forEach で走査
// 遅延:lazy.map

ネストされたコレクション

コレクションの組み合わせ。多次元配列、辞書内の配列、構造体のコレクション。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 2 次元配列:
var grid = [[Int]]()
grid.append([1, 2, 3])
grid.append([4, 5, 6])
print(grid[0][1]) // 2
// 繰り返し初期化:
var matrix = [[Int]](
repeating: [Int](repeating: 0, count: 3), count: 3)
matrix[1][2] = 9
// 辞書の中の配列:
var groups: [String: [String]] = [:]
groups["dev"].append("nick") // エラー
// 事前に初期化が必要:
groups["dev"] = ["nick"]
groups["dev", default: []].append("tom")
// 構造体のコレクション:
struct Task { var title: String; var done = false }
var tasks: [Task] = [Task(title: "a")]
tasks[0].done = true
// 配列の辞書:
let scores: [[String: Int]] = [
["math": 90], ["eng": 85]
]
print(scores[0]["math"] ?? 0)

Range

半開区間 ..<、閉区間 ...。配列のスライスとループ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 範囲の型:
let closed = 1...5 // 5 を含む
let halfOpen = 1..<5 // 5 を含まない
// ループ:
for i in 1...3 {
print(i)
}
// 配列のスライス:
let arr = [10, 20, 30, 40]
let sub = arr[1...2] // [20, 30]
// 片側範囲:
let head = arr[..<2] // [10, 20]
let tail = arr[2...] // [30, 40]
// 包含チェック:
(1...5).contains(3) // true
// ステップ:
for i in stride(from: 0, to: 10, by: 2) {
print(i) // 0 2 4 6 8
}
// 反転:
for i in (1...3).reversed() {}
// 境界:
// 空の範囲:1..<1
// 浮動小数点の範囲(制限が多い)
// Range は添字とパターンマッチに使用

9.メモリ管理

ARC の参照カウント、弱参照、循環参照とメモリ最適化。

ARC

自動参照カウントでクラスインスタンスを管理。参照カウントが 0 になると解放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Device {
var name = ""
deinit {
print("\(name) 释放")
}
}
// 参照カウント:
var d1: Device? = Device() // count 1
d1?.name = "phone"
var d2 = d1 // count 2
var d3 = d1 // count 3
d1 = nil // count 2
d2 = nil // count 1
d3 = nil // count 0、解放
// 規則:
// 強い参照ごとに +1
// カウントが 0 で自動解放
// deinit で解放を観測できる
// 値型(struct)は ARC に関与しない
// 手動管理は不要
// 弱参照はカウントしない

weak/unowned

弱参照はカウントを増加させない。循環参照を防ぐ。解放済みアクセスの対処。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Parent {
var child: Child?
}
class Child {
// weak は親を保持しない:
weak var parent: Parent?
}
var parent: Parent? = Parent()
var child: Child? = Child()
parent?.child = child
child?.parent = parent
// 循環は解消された:
parent = nil
// child の parent は自動的に nil になる
// weak の特徴:
// var 必須、オプショナル必須
// オブジェクト解放で自動的に nil
// unowned:
// class CreditCard {
// unowned let owner: Person
// }
// unowned は常に存在すると仮定
// 解放済み unowned へのアクセスはクラッシュ
// 優先順位:weak > unowned

自動解放プール

autoreleasepool で解放を遅延。大規模ループのメモリ制御。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 大量のループ:
var results: [String] = []
// ループの外にプール:
for i in 0..<1000 {
autoreleasepool {
// 一時オブジェクトはここで解放:
let temp = "临时数据 \(i)"
results.append(temp)
// 他の一時オブジェクト...
}
}
// 各イテレーションの終わりで解放
// 用途:
// 大きなループのピークメモリ
// 大量の画像・ファイル処理
// 時間のかかるタスクの一時オブジェクト
// 注意:
// 現代の Swift は自動管理が多い
// 手動が必要な時だけ使う
// プール内の return は早期解放
// ARC 下では大抵不要

コピーセマンティクス

値型のコピーは安全。大きな型の最適化。共有と一意性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 値型のコピー:
var arr1 = [1, 2, 3]
var arr2 = arr1 // コピーオンライト
// 変更で本当のコピーが発生:
arr2.append(4)
print(arr1) // [1, 2, 3] のまま
// 大きな文字列・配列・辞書:
// 代入は低コスト(ストレージ共有)
// 変更した時にだけ確保
// 自作の struct も同様に安全:
struct Config {
var theme = "dark"
var font = 14
}
var c1 = Config()
var c2 = c1
c2.theme = "light"
print(c1.theme) // dark
// コピーの原則:
// 値の意味論が意図しない共有を防ぐ
// スレッドセーフ(共有状態なし)
// 共有が必要なら class

循環参照

二つのクラスが互いに強参照して環を形成。メモリリーク。weak で断ち切る。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Owner {
var pet: Pet?
deinit { print("Owner 释放") }
}
class Pet {
// BAD:親を強参照、循環になる
// var owner: Owner?
// GOOD:weak 弱参照
weak var owner: Owner?
deinit { print("Pet 释放") }
}
var owner: Owner? = Owner()
var pet: Pet? = Pet()
owner?.pet = pet
pet?.owner = owner
// 循環がないと正常に解放:
owner = nil
pet = nil
// 診断:
// Instruments のリーク検査
// deinit が呼ばれない = リーク
// 他の循環の場面:
// クロージャが self を強参照
// [weak self] で解消
// Timer・Delegate に注意

スタックとヒープ

値型はスタック、参照型はヒープ。割り当てとパフォーマンス。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// スタックに確保:
struct Point { var x = 0 }
let p = Point() // スタック(多くの場合)
// ヒープに確保:
class Box { var v = 0 }
let b = Box() // ヒープ
// 大きな構造体の最適化:
// 一定サイズを超えるとヒープにも
// 配列の要素はインライン格納
// 性能への影響:
// スタック確保は高速、自動解放
// ヒープ確保は ARC 管理が必要
// 多数の小さなオブジェクト
// は struct が class より優れる
// 構造体の配列は連続メモリ:
struct Item { var id = 0 }
let items = [Item(), Item()] // 連続
// 参考:
// 値型はデフォルトでスタック
// エスケープするとヒープ
// 過度に気にする必要なし

lazy ストレージ

lazy で初期化を遅延。使用時に作成。重い属性の最適化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// lazy プロパティ:
class DataLoader {
// 使う時にだけロード:
lazy var config: [String: String] = {
print("加载配置")
return ["env": "dev"]
}()
}
let loader = DataLoader()
// アクセスしないと実行されない:
print("已创建")
// 最初のアクセスで実行:
print(loader.config)
// 以降は直接返す:
print(loader.config)
// 特徴:
// 一度だけ初期化
// スレッドセーフでない(並行は注意)
// クロージャ内で self を使える
// 適する:
// 高コストな構築
// 使われないかもしれないプロパティ
// 他のプロパティに依存する初期化
// グローバル変数も lazy

メモリの最適化

ヒープ割り当ての削減、バッファの再利用、大きなコピーの回避。パフォーマンスチューニング。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 一時的な確保を減らす:
// 可変文字列を再利用:
var buffer = ""
for i in 0..<1000 {
// 連結ではなく追記:
buffer.append("\(i),")
}
// 容量を事前確保:
var nums: [Int] = []
nums.reserveCapacity(1000)
for i in 0..<1000 {
nums.append(i)
}
// 不要なコピーを避ける:
// inout で大きな配列を変更
// ArraySlice で共有
// 値型に注意
// 大きな構造体は参照で渡す
// withUnsafeBytes を使う
// 検査ツール:
// Instruments Allocations
// 時間分析を優先
// 早すぎる最適化はしない

10.クラスとプロトコル

class、継承、プロトコル、拡張とプロトコル指向プログラミング。

class の定義

クラスはプロパティとメソッドを定義。参照型。イニシャライザ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Animal {
var name: String
// イニシャライザ:
init(name: String) {
self.name = name
}
// メソッド:
func speak() {
print("\(name) 叫了一声")
}
}
// インスタンス化:
let dog = Animal(name: "旺财")
dog.speak()
// 参照の意味論:
let same = dog
same.name = "小黑"
print(dog.name) // 小黑
// プロパティのデフォルト値:
class Counter {
var count = 0
init() {}
}
// deinit デストラクタ:
deinit {
print("实例释放")
}
// クラスは継承・拡張が可能
// struct との対比
// クラスは全ての格納プロパティを init で保証

継承

サブクラスはスーパークラスを継承。格納プロパティ、メソッド。最終クラスは final。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Vehicle {
var speed = 0
func describe() -> String {
"速度 \(speed)"
}
}
// サブクラス:
class Car: Vehicle {
var wheels = 4
override func describe() -> String {
"汽车 \(wheels) 轮,\(super.describe())"
}
}
let car = Car()
car.speed = 100
print(car.describe())
// 継承の規則:
// 単一継承
// override の上書きは必ず明記
// 親クラスへのアクセスは super
// 継承できないもの:
// final class は継承不可
// private プロパティは見えない
// 指定・簡易イニシャライザ
// 組合せとプロトコルを優先

メソッドのオーバーライド

override でメソッド、プロパティ、イニシャライザを上書き。super で親クラスを呼び出し。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Base {
var label = "base"
func greet() {
print("Base: \(label)")
}
// 上書き可能なプロパティ:
var value: Int {
get { 10 }
}
}
class Sub: Base {
// メソッドの上書き:
override func greet() {
super.greet() // 先に親クラスを呼ぶ
print("Sub 额外处理")
}
// プロパティの上書き:
override var value: Int {
get { super.value + 5 }
}
}
let s = Sub()
s.greet()
print(s.value)
// 規則:
// override は必須
// 可読性:
// final が上書きを防ぐ
// final func で再上書きを防ぐ
// プロパティ上書きは get/set/willSet/didSet
// 静的メソッドは上書き可能

プロトコル

protocol で要件を定義。型が準拠。プロトコル指向設計の中核。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// プロトコルを定義:
protocol Greetable {
var name: String { get }
func greet() -> String
}
// プロトコルに準拠:
struct Person: Greetable {
var name: String
func greet() -> String {
"你好,\(name)!"
}
}
struct Robot: Greetable {
var name: String
func greet() -> String {
"哔,\(name) 就绪"
}
}
// プロトコル型として使用:
let things: [Greetable] = [
Person(name: "Nick"),
Robot(name: "R2"),
]
for t in things {
print(t.greet())
}
// プロトコルは継承可能:
protocol NamedGreetable: Greetable {
var age: Int { get }
}
// プロトコルのデフォルト実装は拡張で

プロトコル拡張

extension がデフォルト実装を提供。制約付き拡張。プロトコルのデフォルトメソッド。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// プロトコル:
protocol Describable {
var description: String { get }
}
// デフォルト実装:
extension Describable {
var description: String {
"描述:" + Self.self.description
}
}
// 制約付き拡張:
extension Describable where Self: Equatable {
func isSameAs(_ other: Self) -> Bool {
self == other
}
}
// 型にプロトコルを追加:
struct Cat: Describable {
var name: String
}
// デフォルト実装を使う:
let cat = Cat(name: "喵")
print(cat.description)
// プロトコル + 拡張:
// デフォルトメソッドでボイラープレートを削減
// プロトコル継承の組合せ
// where で制約を絞る
// プロトコル指向プログラミングの基礎

拡張

extension は既存の型にメソッドを追加する。カテゴリ別にコードを整理する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 組み込み型を拡張:
extension String {
// メソッドを追加:
func isEmail() -> Bool {
self.contains("@")
}
// 計算プロパティ:
var wordCount: Int {
self.split(separator: " ").count
}
}
print("[email protected]".isEmail())
print("hi there".wordCount)
// 自作の型を拡張:
struct Point { var x: Double }
extension Point {
func doubled() -> Point {
Point(x: x * 2)
}
}
// 拡張でプロトコルに準拠:
extension Point: Equatable {}
// グループ化:
// 1 つの型に複数の extension
// 機能ごとに分類
// 格納プロパティは追加できない
// 既存の実装は変更できない

プロパティオブザーバ

willSet/didSet でプロパティの変更を監視する。UI 更新やバリデーションに使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var user: String = "" {
willSet {
print("即将改为 \(newValue)")
}
didSet {
print("已从 \(oldValue) 改为 \(user)")
}
}
user = "Nick"
user = "Tom"
// 監視のログを出力
// 適用場面:
// 画面バインディングの更新
// データの検証
// 変更の記録
// 規則:
// 初期化では発火しない
// クラスと struct の両方で可能
// 格納プロパティのみ監視可能
// 計算プロパティは get/set
// willSet/didSet の引数は名前を付けられる
// 並行アクセスはスレッドに注意

計算プロパティ

get/set で算出する。値は保持しない。派生データ向け。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct Circle {
var radius: Double
// 計算プロパティ:
var area: Double {
// 読み取り専用:
Double.pi * radius * radius
}
// 読み書き:
var diameter: Double {
get {
radius * 2
}
set {
radius = newValue / 2
}
}
}
var c = Circle(radius: 5)
print(c.area) // 78.54
print(c.diameter) // 10
c.diameter = 20
print(c.radius) // 10
// 特徴:
// アクセスの度に再計算
// ストレージを持たない
// 派生値は常に一貫
// get は必須
// set は newValue を使う
// 引数名のカスタマイズも可能
// 格納プロパティとの対比

11.エラー処理

throws、do-catch、try、defer とカスタムエラーについて。

Error プロトコル

Error プロトコルはエラー型を示す。列挙でエラーカテゴリを定義する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// エラーの列挙型:
enum FileError: Error {
case notFound
case noPermission
case invalidData(String)
}
// 単純なエラー:
struct SimpleError: Error {
let message: String
}
// 分類したエラー:
enum LoginError: Error {
case emptyUsername
case wrongPassword
case tooManyAttempts
}
// 用途:
// 投げられるエラーを表す
// 情報を保持:
// case invalidData(String)
// エラーは比較可能:
// Equatable の導出も可能
// LocalizedError で説明を提供
// エラーは明確に分類して設計

throws

throws は送出可能な関数をマークする。throw でエラーを投げる。呼び出し側で処理が必要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// エラーを投げる関数:
func divide(_ a: Int, by b: Int) throws -> Int {
guard b != 0 else {
throw DivideError.zeroDivision
}
return a / b
}
enum DivideError: Error {
case zeroDivision
}
// 呼び出し時は必ず処理する:
// 1. try! 危険
// let x = try! divide(1, by: 0)
// 2. try? でオプショナル化
let x = try? divide(1, by: 0)
print(x) // nil
// 3. try + do-catch
// 4. 呼び出し元へ伝播
func safeDivide() throws -> Int {
try divide(10, by: 2)
}
// 連鎖する throw:
// 関数を throws と宣言すればそのまま透過

do-catch

do ブロック内で try を使う。catch でエラーを捕捉する。複数分岐に対応。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
enum CalcError: Error {
case zeroDiv
case overflow
}
func compute() throws -> Int {
throw CalcError.zeroDiv
}
// キャッチ:
do {
let result = try compute()
print(result)
} catch CalcError.zeroDiv {
print("除以零")
} catch CalcError.overflow {
print("溢出")
} catch {
print("其他错误: \(error)")
}
// エラーをバインド:
// catch let e as CalcError
// 1 つの do で複数の try
// catch は具体的なものから一般的なものへ
// error は暗黙の変数
// 未キャッチ:
// 呼び出し元へ伝播
// catch がない場合は throws が必要

try の形式

try は catch が必要。try? はオプショナルに変換。try! は強制(クラッシュリスク)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func risky() throws -> Int { 42 }
// try:do-catch か伝播が必須
// 構文の要求:
func caller() throws {
let v = try risky()
print(v)
}
// try?:失敗は nil に
let a = try? risky() // Int?
print(a ?? 0)
// try!:失敗するとクラッシュ
// 確実に失敗しない場合に適する:
let b = try! risky()
print(b)
// 例:
// let data = try? Data(contentsOf: url)
// 強制アンラップのトレードオフ:
// try! は失敗で即クラッシュ
// 本番では慎重に使う
// テスト・確実な場面では使用可
// try? は「失敗してもよい」場面に適する

カスタムエラー

エラーにコンテキストを載せる。LocalizedError で説明を追加。エラーコード。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// コンテキスト付き:
enum NetworkError: LocalizedError {
case timeout(Int)
case serverError(code: Int, message: String)
// ローカライズされた説明:
var errorDescription: String? {
switch self {
case .timeout(let s):
return "超时 \(s) 秒"
case .serverError(let code, let msg):
return "服务器 \(code): \(msg)"
}
}
}
// 使用:
func fetch() throws {
throw NetworkError.serverError(code: 500, message: "内部错误")
}
do {
try fetch()
} catch let e as NetworkError {
print(e.errorDescription ?? e)
}
// 任意の Error をキャッチ:
// catch let err { print(err.localizedDescription) }
// 関連値でコンテキストを提供
// プロトコルの組合せ:
// Equatable でエラーを比較

defer クリーンアップ

defer はスコープ終了時に実行される。リソース解放に使う。逆順で実行される。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func processFile() throws {
print("打开文件")
// 遅延クリーンアップ:
defer {
print("关闭文件")
}
print("处理数据")
// 途中でエラーになっても defer は実行される
// throw SomeError()
print("正常结束")
}
try? processFile()
// 複数の defer は逆順:
defer { print("A") }
defer { print("B") }
// B の後に A が出力される
// 用途:
// ファイル・接続のクローズ
// ロック解除、状態の復元
// 一時リソースのクリーンアップ
// 早期 return でも実行される
// 注意:
// defer 内で戻り値の読み書きをしない

致命的エラー

fatalError は回復不能なクラッシュ。precondition で条件をチェック。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 回復不能:
// fatalError("无法继续")
// precondition:
func process(_ n: Int) {
precondition(n > 0, "参数必须为正数")
print(n)
}
process(5)
// process(-1) // クラッシュ(precondition 不成立)
// デバッグアサーション:
assert(true, "仅调试检查")
// その他:
// assert はデバッグビルドで有効
// precondition は常に有効
// 適用場面:
// 不変条件が破られた時
// プログラムが続行不能なバグ
// 開発期に早く失敗させる
// 本番ではエラーハンドリング
// 乱用しない

Result 型

Result<Success, Failure> で成功/失敗を明示する。throws を避けられる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Result の使用:
func fetchData() -> Result<[String], Error> {
let ok = true
if ok {
return .success(["a", "b"])
} else {
return .failure(NetworkError.timeout(30))
}
}
enum NetworkError: Error {
case timeout(Int)
}
// 処理:
let result = fetchData()
switch result {
case .success(let data):
print(data)
case .failure(let error):
print(error)
}
// メソッド:
// result.map { ... }
// result.flatMap { ... }
// try result.get() throws に変換
// 場面:
// 非同期コールバックの戻り値
// 例外ではないエラーの流れ
// Result<_, Never> との組合せ
// エラー結果の配列を格納

12.入出力

コマンドライン入力、ファイル読み書き、FileManager と Codable について。

入力の読み込み

readLine でコマンドライン入力を読む。ループで対話的に処理。数値へのパース。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Foundation
// 1 行読み取り:
print("请输入名字:", terminator: "")
if let name = readLine() {
print("你好,\(name)!")
}
// 数値の読み取り:
print("输入年龄:", terminator: "")
if let input = readLine(), let age = Int(input) {
print("年龄 \(age)")
}
// ループでの対話:
while let line = readLine(), !line.isEmpty {
print("收到: \(line)")
}
// 一括パイプ入力:
// echo "data" | swift main.swift
// readLine は EOF で nil を返す
// 標準エラー出力:
// FileHandle.standardError
// 出力:
// print は標準出力へ

ファイルの読み込み

String(contentsOf:) でファイルを読み込む。Data はバイナリ用。行ごとに読み込み。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// 文字列として読み取り:
let path = "data.txt"
do {
let content = try String(contentsOfFile: path, encoding: .utf8)
print(content)
} catch {
print("读取失败: \(error)")
}
// Data として読み取り:
let data = try? Data(contentsOf: URL(fileURLWithPath: path))
// 1 行ずつ処理:
let text = "line1\nline2\n"
for line in text.components(separatedBy: .newlines) {
print(line)
}
// 相対パスの注意:
// カレントディレクトリ基準
// サンドボックスでは Document ディレクトリが必要
// エンコーディング:
// .utf8 / .unicode
// 大きなファイルはストリーム読み取り:
// FileHandle + read(upToCount:)

ファイルの書き込み

文字列と Data の書き込み。追記モード。アトミック書き込み。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Foundation
let content = "hello\nworld\n"
// 上書き書き込み:
let url = URL(fileURLWithPath: "out.txt")
try? content.write(to: url, atomically: true, encoding: .utf8)
// Data の書き込み:
let data = Data(content.utf8)
try? data.write(to: url)
// 追記書き込み:
if let handle = try? FileHandle(forWritingTo: url) {
handle.seekToEndOfFile()
handle.write(Data("more\n".utf8))
try? handle.close()
}
// パスの処理:
let dir = URL(fileURLWithPath: "data")
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
// エラー処理:
// try? はエラーを無視
// 本番では do-catch
// アトミック書き込みで破損を防ぐ

FileManager

ファイルシステム操作。ディレクトリの作成、削除、移動、存在判定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
let fm = FileManager.default
// 存在確認:
let exists = fm.fileExists(atPath: "data.txt")
// ディレクトリ作成:
try? fm.createDirectory(
atPath: "data/sub", withIntermediateDirectories: true)
// 移動・リネーム:
try? fm.moveItem(atPath: "a.txt", toPath: "b.txt")
// コピー:
try? fm.copyItem(atPath: "b.txt", toPath: "c.txt")
// 削除:
try? fm.removeItem(atPath: "c.txt")
// ディレクトリの一覧:
if let items = try? fm.contentsOfDirectory(atPath: ".") {
print(items)
}
// 属性:
if let attrs = try? fm.attributesOfItem(atPath: "data.txt") {
print(attrs[.size] ?? "?")
}
// ディレクトリ走査:
// enumerator(atPath:)
// サンドボックスパス:
// fm.urls(for: .documentDirectory, in: .userDomainMask)

出力制御

print の形式。stderr。区切りと終端。文字列表現。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
// 基本:
print("普通输出")
print(1, 2, 3) // スペース区切り
print("a", "b", separator: "-")
print("x", terminator: "") // 改行しない
print() // 改行
// 標準エラー:
FileHandle.standardError.write(
Data("错误信息\n".utf8))
// デバッグ説明:
debugPrint("debug")
// カスタム出力:
struct Item: CustomStringConvertible {
var name: String
var description: String {
"Item(\(name))"
}
}
print(Item(name: "a"))
// CustomDebugStringConvertible:
// デバッグでより詳細
// dump で深く出力:
dump([1, 2, 3])

Data 操作

Data はバイトコンテナ。文字列と相互変換。base64 のエンコード/デコード。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
// 文字列から Data:
let str = "hello"
let data = Data(str.utf8)
// Data から文字列:
let back = String(data: data, encoding: .utf8)
print(back ?? "")
// base64:
let encoded = data.base64EncodedString()
print(encoded) // aGVsbG8=
let decoded = Data(base64Encoded: encoded)
// バイト操作:
var bytes: [UInt8] = [0x68, 0x69]
let d = Data(bytes)
print(Array(d)) // [104, 105]
// 追記:
var buffer = Data()
buffer.append(Data([1, 2]))
buffer.append(Data("a".utf8))
// 16 進数:
let hex = data.map { String(format: "%02x", $0) }.joined()
print(hex) // 68656c6c6f
// サブ範囲の読み取り:
// data.subdata(in: 0..<2)

JSONSerialization

JSON を辞書/配列に変換。逆方向のシリアライズ。JSONSerialization クラス。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
// JSON 文字列:
let jsonStr = "{\"name\": \"Nick\", \"age\": 30, \"tags\": [\"dev\"]}"
let jsonData = Data(jsonStr.utf8)
// Any として解析:
let obj = try? JSONSerialization.jsonObject(
with: jsonData)
// 辞書に変換:
if let dict = obj as? [String: Any] {
print(dict["name"] ?? "")
let age = dict["age"] as? Int ?? 0
let tags = dict["tags"] as? [String] ?? []
print(age, tags)
}
// 逆シリアライズ:
let payload: [String: Any] = [
"name": "Tom", "age": 25
]
if JSONSerialization.isValidJSONObject(payload) {
let data = try? JSONSerialization.data(withJSONObject: payload)
let str = String(data: data!, encoding: .utf8)
print(str ?? "")
}
// 現代では Codable を推奨

Codable

Codable で自動シリアライズ。JSONEncoder/Decoder。プロトコル駆動。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// 型が Codable に準拠:
struct User: Codable {
var name: String
var age: Int
var tags: [String]
}
// エンコード:
let user = User(name: "Nick", age: 30, tags: ["dev"])
let encoder = JSONEncoder()
let data = try? encoder.encode(user)
let json = String(data: data!, encoding: .utf8)
print(json ?? "")
// デコード:
let jsonStr = "{\"name\": \"Tom\", \"age\": 25, \"tags\": []}"
let decoder = JSONDecoder()
if let u = try? decoder.decode(User.self, from: Data(jsonStr.utf8)) {
print(u.name)
}
// フィールドのマッピング:
// CodingKeys でリネーム
// snake_case:
// decoder.keyDecodingStrategy = .convertFromSnakeCase
// 配列のデコード:[User].self
// 日付処理には戦略が必要

13.よくある落とし穴

Swift 初心者が最も踏みやすい落とし穴と正しい書き方。

強制アンラップ

! でアンラップすると nil でクラッシュ。オプショナルバインディングと Nil-Coalescing 演算子を優先。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// BAD:強制アンラップ
var name: String? = nil
// print(name!) // クラッシュ
// GOOD:オプショナルバインディング
if let name = name {
print(name)
}
// GOOD:nil合体演算子
let display = name ?? "匿名"
print(display)
// GOOD:guard で早期アンラップ
func show(_ v: String?) {
guard let v = v else {
print("无值")
return
}
print(v)
}
// いつ安全に ! を使うか:
// nil でないことが確定(代入済み)
// テスト・プロトタイプ
// それ以外は避ける
// try! も同様に慎重に
// クラッシュ情報は分かりにくい
// 本番コードでは ! を控える

オプショナルチェイン

?. でチェインアクセス。途中で nil なら全体も nil。代入にも使える。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Address { var city = "北京" }
class Person {
var address: Address?
}
let p = Person()
// GOOD:オプショナルチェーン
let city = p.address?.city // String?
print(city ?? "无地址")
// BAD:途中が nil でないと仮定
// p.address!.city // クラッシュの可能性
// 複数層のチェーン:
// p.address?.city.count
// 代入のオプショナルチェーン:
p.address?.city = "上海" // nil なら何もしない
// アドレスを代入:
p.address = Address()
p.address?.city = "上海"
print(p.address?.city ?? "")
// メソッド呼び出しのチェーン:
// p.address?.method()
// チェーン内に値があるか判定:
if let c = p.address?.city {
print(c)
}
// よくある誤解:
// チェーンはオプショナル型を返す
// 自動ではアンラップされない
// 強制チェーン !. との対比

クロージャの循環参照

クロージャが self を強参照でキャプチャし循環する。キャプチャリスト [weak self] で回避。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Loader {
var data: String = ""
var completion: (() -> Void)?
func setup() {
// BAD:クロージャが self を強キャプチャ
// completion = {
// self.data = "loaded"
// }
// GOOD:[weak self]
completion = { [weak self] in
self?.data = "loaded"
// self 解放後は自動的に nil
}
}
deinit { print("Loader 释放") }
}
// 使用:
var l: Loader? = Loader()
l?.setup()
// クロージャ実行または解放:
l = nil // 正常に解放
// 他の解消方法:
// [unowned self] は存在を仮定
// クロージャ外で強参照して手動で nil
// よくある場面:
// ネットワークコールバック、Timer、アニメーション

配列の範囲外アクセス

インデックス範囲外でクラッシュ。first/last/prefix で安全アクセス。境界をチェック。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let nums = [1, 2, 3]
// BAD:範囲外インデックス
// print(nums[5]) // クラッシュ
// GOOD:安全なアクセス
if nums.indices.contains(5) {
print(nums[5])
}
// 安全なメソッド:
let first = nums.first ?? 0
let last = nums.last ?? 0
// 安全に要素を取得する拡張:
extension Array {
func safe(_ index: Int) -> Element? {
indices.contains(index) ? self[index] : nil
}
}
print(nums.safe(1) ?? 0) // 2
print(nums.safe(9) ?? 0) // 0
// 空配列の注意:
// nums[0] もクラッシュ
// 走査は indices か enumerated
// 動的インデックスは先にチェック

mutating

struct のメソッドでプロパティ変更には mutating が必要。値型は不変コピーされる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct Counter {
var count = 0
// 自身を変更する必要がある:
mutating func increment() {
count += 1
}
// 読み取り専用メソッドは不要:
func current() -> Int {
count
}
}
var c = Counter()
c.increment()
c.increment()
print(c.current()) // 2
// BAD:定数インスタンスで mutating を呼ぶ
// let cc = Counter()
// cc.increment() // エラー
// GOOD:var の変数インスタンス
var cc2 = Counter()
cc2.increment()
// 理由:
// mutating は self を変更する
// 定数インスタンスは不変
// 規則:
// struct/enum では mutating が必要
// class では不要
// 添字・プロトコルメソッドにも適用
// 値の意味論の現れ

文字列インデックス

文字列は整数インデックス不可。文字はマルチバイト。index メソッドを使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let s = "你好 Swift"
// BAD:整数の添字
// let c = s[0] // エラー
// 文字数はバイト数と等しくない
// GOOD:インデックス API
let first = s.startIndex
let c = s[first] // 你
print(c)
// オフセット:
let second = s.index(after: first)
print(s[second])
// 指定オフセット:
let idx = s.index(s.startIndex, offsetBy: 3)
print(s[idx])
// 文字の配列に変換:
let chars = Array(s)
print(chars[0]) // 你
// 走査:
for c in s { print(c) }
// スライス:
// s[..<idx]
// マルチバイト安全
// String.Index が境界を保証

プロトコルと Self

プロトコル内で Self で制約。associatedtype。型消去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// プロトコルの Self 制約:
protocol EquatableLike {
func isEqualTo(_ other: Self) -> Bool
}
// 準拠:
struct Box: EquatableLike {
var value: Int
func isEqualTo(_ other: Box) -> Bool {
self.value == other.value
}
}
// BAD:Self を含むプロトコルを型として使う
// func compare(a: EquatableLike, b: EquatableLike) // エラー
// GOOD:ジェネリクスの制約
func compare<T: EquatableLike>(_ a: T, _ b: T) -> Bool {
a.isEqualTo(b)
}
// 関連型:
// protocol Container {
// associatedtype Item
// }
// 型消去:
// AnyEquatable でラップ
// 動的ポリモーフィズムが必要な時
// ジェネリクスか型消去を使う

暗黙的アンラップ

T! は暗黙的アンラップオプショナル。未初期化で使うとクラッシュ。注意して使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// BAD:暗黙アンラップの未代入アクセスでクラッシュ
var name: String! = nil
// 直接使用:
// print(name) // クラッシュ(未代入)
// 代入後:
name = "Nick"
print(name) // 自動アンラップ
// 用途:
// IBOutlet 接続
// 初期化後に必ず値がある
// 2 段階の初期化
// リスク:
// 未代入アクセスでクラッシュ
// オプショナルの安全性を失う
// 新規コードでは推奨しない
// GOOD:通常のオプショナル
var safeName: String?
// アンラップ:
if let n = safeName {
print(n)
}
// 強制的な場面:
// 初期化済みを確定してからアクセス
// または let 定数に変更

14.並行処理

GCD、async/await、Task、actor による並行モデル。

GCD の基本

DispatchQueue はシリアル/コンカレントキュー。グローバルとメインキュー。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
// メインキュー:
let main = DispatchQueue.main
// グローバル並行キュー:
let background = DispatchQueue.global(qos: .background)
// シリアルキューの作成:
let serial = DispatchQueue(label: "com.app.serial")
// 並行キューの作成:
let concurrent = DispatchQueue(
label: "com.app.concurrent", attributes: .concurrent)
// タスクの送信:
background.async {
// バックグラウンドで実行...
print("后台任务")
}
// 同期送信:
// serial.sync { ... }
// 遅延実行:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
print("1 秒后")
}
// QoS 優先度:
// .userInteractive / .userInitiated
// .utility / .background

Dispatch Group

DispatchGroup で複数タスクの完了を待つ。バッチ並行の集約に使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
let group = DispatchGroup()
let queue = DispatchQueue.global()
// グループに入る:
// 複数の非同期タスク
for i in 0..<3 {
group.enter()
queue.async {
print("任务 \(i) 执行")
// 時間のかかる処理を模擬
Thread.sleep(forTimeInterval: 0.2)
group.leave()
}
}
// 全て完了のコールバック:
group.notify(queue: .main) {
print("所有任务完成")
}
// 同期待機:
// group.wait()
// タイムアウト待機:
// group.wait(timeout: .now() + 5)
// 注意:
// enter と leave は必ず対にする
// マルチスレッドで安全
// 並行ダウンロード・一括処理に適する

async/await

モダンな Swift の並行構文。async 関数とサスペンド。構造化並行処理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import Foundation
// 非同期関数:
func fetchData() async -> String {
// ネットワーク遅延を模擬
try? await Task.sleep(nanoseconds: 1_000_000_000)
return "数据"
}
// 呼び出し:
func load() async {
let data = await fetchData()
print(data)
}
// エラーを投げる非同期関数:
func risky() async throws -> Int {
try await Task.sleep(nanoseconds: 100_000_000)
return 42
}
// 呼び出し:
func caller() async {
if let v = try? await risky() {
print(v)
}
}
// 注意:
// 同期コンテキストでは非同期関数を呼べない
// Task { } で包む
// 古いコールバックスタイルと相互変換

async let

複数の非同期呼び出しを並行実行。独立した子タスクにバインド。並列に待つ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// 3 つの独立した非同期タスク:
func loadUser() async -> String { "用户" }
func loadPosts() async -> String { "帖子" }
func loadComments() async -> String { "评论" }
// async let で並行:
func loadAll() async -> String {
async let user = loadUser()
async let posts = loadPosts()
async let comments = loadComments()
// 全て並行に実行:
let result = await "\(user) \(posts) \(comments)"
return result
}
// 使用:
// 3 つのタスクが同時に走る
// await で全ての結果を集約
// 注意:
// async let はバインド後必ず await
// 依存関係:
// 依存があるなら順次 await
// 依存がないなら async let
// 独立した API の並行リクエストに適する

Task

Task は並行ユニットを作成。コンテキストを継承。キャンセルと優先度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import Foundation
// タスクの作成:
func run() async {
let task = Task {
try await Task.sleep(nanoseconds: 500_000_000)
return "完成"
}
let result = await task.value // String
print(result)
}
// メインスレッドへ派遣:
// @MainActor func updateUI()
// Task { @MainActor in ... }
// タスクのキャンセル:
task.cancel()
// キャンセルの確認:
try Task.checkCancellation()
// 構造化タスクグループ:
await withTaskGroup(of: String.self) { group in
for i in 0..<3 {
group.addTask {
"任务\(i)"
}
}
for await r in group {
print(r)
}
}
// 優先度:Task(priority: .high)

actor

actor は状態を隔離する。データ競合を回避。非同期メソッドでアクセス。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import Foundation
// actor の定義:
actor BankAccount {
private var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
func getBalance() -> Double {
balance
}
}
// 使用(必ず await):
func test() async {
let account = BankAccount()
// 分離された状態へのアクセスは await が必要:
await account.deposit(100)
let balance = await account.getBalance()
print(balance)
}
// 並行安全性:
// コンパイラが直列アクセスを保証
// データ競合を回避
// 対比:
// クラスは自分でロックが必要
// actor は自動で分離
// 注意:
// actor のメソッドはデフォルトで async 化
// await での呼び出しが必要
// 共有可変状態に適する

メインスレッド

UI 操作はメインスレッド必須。スレッド間移動。メインスレッド確認。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// メインスレッドの判定:
if Thread.isMainThread {
print("在主线程")
}
// メインスレッドへ戻る:
DispatchQueue.main.async {
// UI 更新
print("主线程执行")
}
// メインスレッドでの同期実行:
// DispatchQueue.main.sync { }
// デッドロック注意:
// メインスレッドから自分に sync するとデッドロック
// 現代の Swift:
@MainActor
func updateUI() {
// メインスレッドに分離
}
// 非同期コンテキストから呼び出し:
// await MainActor.run { ... }
// 規則:
// UI 更新・アニメーションはメインスレッド
// 時間のかかるタスクはバックグラウンド
// バックグラウンドからメインへ戻って更新

スレッドセーフ

可変状態の並行アクセスを保護。ロック、キュー、アトミック操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import Foundation
// シリアルキューで保護:
final class SafeCounter {
private let queue = DispatchQueue(label: "counter")
private var _count = 0
var count: Int {
queue.sync { _count }
}
func increment() {
queue.sync { _count += 1 }
}
}
let counter = SafeCounter()
for _ in 0..<100 {
DispatchQueue.global().async {
counter.increment()
}
}
// ロック:
// NSLock
let lock = NSLock()
lock.lock()
// クリティカルセクション...
lock.unlock()
// アトミック:
// OSAllocatedUnfairLock
// 現代の解決策:
// actor の分離が最も安全
// 読み取り専用の定数は保護不要
// 不変データを優先

15.ネットワーク

URLSession、HTTP リクエスト、非同期ネットワークと JSON の連携。

URLSession

URLSession でネットワークリクエストを発行。設定とデリゲート。セッションタイプ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Foundation
// デフォルトセッション:
let session = URLSession.shared
// カスタム設定:
let config = URLSessionConfiguration.default
config.timeoutIntervalForRequest = 30
config.waitsForConnectivity = true
let custom = URLSession(configuration: config)
// リクエスト:
let url = URL(string: "https://api.example.com")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
// セッションの種類:
// .default / .ephemeral / .background
// ephemeral はキャッシュ・Cookie を保存しない
// background はバックグラウンド転送
// データタスク:
let task = session.dataTask(with: request)
// async/await と組み合わせて使用

GET リクエスト

GET を発行。async/await でデータを取得。レスポンス状態を処理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Foundation
// 非同期 GET:
func fetchContent() async throws -> String {
let url = URL(string: "https://api.example.com/data")!
let (data, response) = try await URLSession.shared.data(from: url)
// ステータスコードの確認:
guard let http = response as? HTTPURLResponse,
http.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return String(data: data, encoding: .utf8) ?? ""
}
// 使用:
func load() async {
do {
let content = try await fetchContent()
print(content)
} catch {
print("请求失败: \(error)")
}
}
// クエリパラメータ:
var comps = URLComponents(string: "https://api.example.com/search")!
comps.queryItems = [URLQueryItem(name: "q", value: "swift")]
// タイムアウト:
// request.timeoutInterval = 10
// 古いコールバック式:
// dataTask(with:completionHandler:)

POST リクエスト

JSON body を送信。フォーム送信。Content-Type を設定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// POST JSON:
func postJSON() async throws {
let url = URL(string: "https://api.example.com/users")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// body:
let payload: [String: Any] = ["name": "Nick", "age": 30]
request.httpBody = try JSONSerialization.data(withJSONObject: payload)
let (data, response) = try await URLSession.shared.data(for: request)
// レスポンスの処理...
print(response)
}
// フォーム:
var form = URLRequest(url: URL(string: "https://x/login")!)
form.httpMethod = "POST"
form.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
form.httpBody = "user=nick&pwd=123".data(using: .utf8)
// 注意:
// 大きな body はストリーム
// レスポンスもステータスコードを確認
// Codable で直接エンコード

非同期ネットワーク

async/await でのネットワーク呼び出し。同時並行リクエスト。タスクのキャンセル。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import Foundation
// 非同期リクエスト + Codable:
struct User: Codable { var id: Int; var name: String }
func fetchUsers() async throws -> [User] {
let url = URL(string: "https://api.example.com/users")!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([User].self, from: data)
}
// 複数リクエストを並行:
func loadAll() async throws -> (User, [String]) {
async let user = fetchUsers()
async let tags: [String] = ["a", "b"]
return (try await user.first!, try await tags)
}
// タスクのキャンセル:
// Task のキャンセルでネットワークリクエストも自動中断
// レスポンスの確認
func fetchWithTimeout() async throws -> Data {
let url = URL(string: "https://api.example.com")!
// タイムアウト:
let config = URLSessionConfiguration.ephemeral
config.timeoutIntervalForRequest = 10
let session = URLSession(configuration: config)
return try await session.data(from: url).0
}
// エラー処理:
// URLError の型で分類

URL の組み立て

URLComponents で URL を安全に組み立てる。クエリパラメータのエンコード。パスの結合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Foundation
// 基本の URL:
var comps = URLComponents()
comps.scheme = "https"
comps.host = "api.example.com"
comps.path = "/v1/search"
// クエリパラメータ:
comps.queryItems = [
URLQueryItem(name: "q", value: "swift 并发"),
URLQueryItem(name: "limit", value: "10"),
URLQueryItem(name: "page", value: "2"),
]
// 自動エンコード:
let url = comps.url!
print(url.absoluteString)
// URL の解析:
if let c = URLComponents(string: url.absoluteString) {
print(c.host ?? "")
for item in c.queryItems ?? [] {
print("\(item.name)=\(item.value ?? "")")
}
}
// 利点:
// 自動でパーセントエンコード
// 中文・特殊文字も安全
// 文字列連結の誤りを回避
// フラグメントの追加:
comps.fragment = "section"

ファイルのダウンロード

ダウンロードタスクでファイルを保存する。進捗の監視。バックグラウンドダウンロード。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import Foundation
// データのダウンロード:
func downloadImage() async throws -> Data {
let url = URL(string: "https://example.com/img.png")!
let (data, _) = try await URLSession.shared.data(from: url)
return data
}
// ファイルに保存:
func save() async throws {
let data = try await downloadImage()
let dir = FileManager.default.temporaryDirectory
let file = dir.appendingPathComponent("img.png")
try data.write(to: file)
print(file.path)
}
// ファイルダウンロードタスク:
let config = URLSessionConfiguration.background(withIdentifier: "dl")
let session = URLSession(configuration: config)
// 進捗の監視:
// デリゲート URLSessionDownloadDelegate
// ダウンロード完了のコールバック
// レジューム(再開)
// background 対応
// 注意:
// 大きなファイルはディスクへ書き込み
// サンドボックスの正しいディレクトリを選択
// メモリを考慮

ネットワーク JSON

JSON をリクエストし Codable でデコードする。日付とその戦略の扱い。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// モデル:
struct Post: Codable {
var id: Int
var title: String
var created: Date
}
// 日付のデコード戦略:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
// スネークケース:
// decoder.keyDecodingStrategy = .convertFromSnakeCase
// リクエスト:
func fetchPosts() async throws -> [Post] {
let url = URL(string: "https://api.example.com/posts")!
let (data, _) = try await URLSession.shared.data(from: url)
return try decoder.decode([Post].self, from: data)
}
// エンコードしてアップロード:
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
// エラー処理:
// DecodingError の分類
// .dataCorrupted / .keyNotFound
// ネットワーク層のエラーは分けて処理
// モデルの検証

ファイルのアップロード

multipart フォームでアップロードする。ファイルの body。境界文字列による区切り。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// multipart アップロード:
func upload(data: Data, filename: String) async throws {
let url = URL(string: "https://api.example.com/upload")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let boundary = "Boundary-\(UUID().uuidString)"
request.setValue(
"multipart/form-data; boundary=\(boundary)",
forHTTPHeaderField: "Content-Type")
// body の構築:
var body = Data()
body.append("--\(boundary)\r\n".data(using: .utf8)!)
let header = "Content-Disposition: form-data; name=\"file\"; filename=\"\(filename)\"\r\n\r\n"
body.append(header.data(using: .utf8)!)
body.append(data)
body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
request.httpBody = body
let (_, response) = try await URLSession.shared.data(for: request)
print(response)
}
// 注意:
// boundary は一意
// 大きなファイルはストリームアップロード
// 進捗は URLSessionUploadTask
// サーバーレスポンスの検証

16.時刻と日付

Date、DateFormatter、カレンダー演算とタイマー。

Date

Date は時点を表す。タイムゾーンに依存しない絶対的な時刻。生成と比較。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// 現在時刻:
let now = Date()
// タイムスタンプ(秒):
let ts = now.timeIntervalSince1970
print(ts)
// タイムスタンプから作成:
let fromTs = Date(timeIntervalSince1970: 1700000000)
// オフセット作成:
let inOneHour = Date().addingTimeInterval(3600)
// 比較:
let earlier = Date(timeIntervalSince1970: 0)
print(now > earlier) // true
// 時間間隔:
let diff = now.timeIntervalSince(earlier)
print(diff) // 秒
// 並べ替え:
let dates = [now, earlier].sorted()
// 注意:
// Date は地域と無関係
// 表示は DateFormatter が必要
// 演算は Calendar を使う
// 秒の手動加減算はしない

日付のフォーマット

DateFormatter で日付を表示する。ローカライズされた書式。カスタム書式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
let now = Date()
let formatter = DateFormatter()
// ローカライズ:
formatter.locale = Locale(identifier: "zh_CN")
formatter.timeZone = .current
// プリセットスタイル:
formatter.dateStyle = .medium // 2026年8月2日
formatter.timeStyle = .short // 上午10:30
print(formatter.string(from: now))
// カスタム形式:
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
print(formatter.string(from: now))
// 書式記号:
// yyyy 年 / MM 月 / dd 日
// HH 24時 / mm 分 / ss 秒
// EEEE 曜日の完全名
formatter.dateFormat = "yyyy年MM月dd日 EEEE"
print(formatter.string(from: now))
// 注意:
// 固定書式では locale を設定
// 表示はプリセットスタイルを優先
// 12/24 時間制

日付のパース

文字列を Date に変換する。固定書式のパース。ISO8601 のパース。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// カスタム書式の解析:
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = .current
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
if let date = formatter.date(from: "2026-08-02 10:30:00") {
print(date)
}
// ISO8601 の解析:
let iso = ISO8601DateFormatter()
if let d = iso.date(from: "2026-08-02T10:30:00Z") {
print(d)
}
// 緩い解析:
formatter.dateFormat = "yyyy/M/d"
// 注意:
// 解析失敗時は nil を返す
// 固定書式は en_US_POSIX を使う
// 曖昧さを避ける
// 入力書式が一致しない場合
// 複数の formatter を試す
// サーバーの ISO は iso8601 を優先

日付コンポーネント

DateComponents で年月日を取り出す。カレンダーコンポーネントの演算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
let calendar = Calendar.current
let now = Date()
// コンポーネントの抽出:
let comps = calendar.dateComponents(
[.year, .month, .day, .weekday], from: now)
print(comps.year ?? 0, comps.month ?? 0, comps.day ?? 0)
print(comps.weekday ?? 0) // 1=日曜...7=土曜
// 個別の抽出:
let year = calendar.component(.year, from: now)
// コンポーネントから日付を作成:
var c = DateComponents()
c.year = 2026
c.month = 8
c.day = 2
let date = calendar.date(from: c)
print(date ?? Date())
// 今日の開始:
let start = calendar.startOfDay(for: now)
// 注意:
// コンポーネントはカレンダー意味論
// タイムゾーンをまたいでも安全
// 手計算の日付の代わりに使う

カレンダー演算

日付への日・月の加減算。翌週/翌月。Calendar による安全な演算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
let calendar = Calendar.current
let now = Date()
// 日数を加算:
let tomorrow = calendar.date(byAdding: .day, value: 1, to: now)!
// 月を加算:
let nextMonth = calendar.date(byAdding: .month, value: 1, to: now)!
// 年を加算:
let nextYear = calendar.date(byAdding: .year, value: 1, to: now)!
// 月初:
let monthStart = calendar.date(from:
calendar.dateComponents([.year, .month], from: now))!
// 次の月曜日:
let monday = calendar.nextDate(
after: now, matching: DateComponents(weekday: 2),
matchingPolicy: .nextTime)!
// 日数の間隔:
let days = calendar.dateComponents(
[.day], from: now, to: tomorrow).day!
print(days) // 1
// 同日かどうか:
calendar.isDate(now, inSameDayAs: tomorrow)
// タイムゾーン:
// calendar.timeZone

時間間隔

コードの所要時間を計測する。TimeInterval は秒単位。パフォーマンス計測。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Foundation
// 計測:
let start = Date()
// タスクを実行...
Thread.sleep(forTimeInterval: 0.3)
let elapsed = Date().timeIntervalSince(start)
print("\(elapsed) 秒") // ~0.3
// 高精度の計測:
let t0 = DispatchTime.now()
// タスク...
let t1 = DispatchTime.now()
let nanos = t1.uptimeNanoseconds - t0.uptimeNanoseconds
print("\(Double(nanos) / 1e6) ms")
// スリープ:
Thread.sleep(forTimeInterval: 0.5)
// 非同期スリープ:
// try await Task.sleep(nanoseconds: 500_000_000)
// TimeInterval = Double の秒
// 注意:
// ウォームアップで初回を無視
// 複数回の平均を取る
// 単位は秒に統一

Timer

タイマーで繰り返し実行する。単発のタイマー。RunLoop に関する注意。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// 繰り返しタイマー:
var count = 0
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
count += 1
print("tick \(count)")
if count >= 3 {
timer.invalidate() // 停止
}
}
// ワンショット:
Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) {
print("2 秒后执行")
}
// 注意:
// scheduledTimer は現在の RunLoop に追加
// メインスレッドで使用可
// バックグラウンドスレッドでは run loop が必要
// 無効化:
// timer.invalidate()
// 強参照を避ける:
// クロージャが self をキャプチャして循環
// [weak self] を使う
// 現代の代替:
// Task.sleep のループ

日付の比較

日付の前後を比較する。同じ日かどうかの判定。並べ替えと検証。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import Foundation
let calendar = Calendar.current
let now = Date()
let past = Date(timeIntervalSinceNow: -3600)
let future = Date(timeIntervalSinceNow: 3600)
// 直接比較:
print(future > now) // true
// 同じ日か:
calendar.isDate(now, inSameDayAs: future)
// 今日・昨日・明日:
calendar.isDateInToday(now)
calendar.isDateInYesterday(past)
calendar.isDateInTomorrow(future)
// 区間の包含:
let range = past...future
print(range.contains(now)) // true
// 並べ替え:
let dates = [future, past, now].sorted()
// 日数の差:
let daysBetween = calendar.dateComponents(
[.day], from: past, to: future).day ?? 0
print(daysBetween)
// 有効期限の確認:
if let expires = Calendar.current.date(byAdding: .hour, value: 2, to: now),
now > expires {
print("已过期")
}

17.プロセスとシステム

コマンドライン引数、環境変数、プロセス操作とファイルパス。

コマンドライン引数

CommandLine で引数を取得する。引数とオプションの処理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Foundation
// 引数の取得:
let args = CommandLine.arguments
print(args) // ["程序名", "arg1", "arg2"]
// 引数の個数:
let count = CommandLine.arguments.count
// シンプルな解析:
let arguments = CommandLine.arguments
var verbose = false
var inputFile = ""
var index = 1
while index < arguments.count {
switch arguments[index] {
case "-v", "--verbose":
verbose = true
case "-f", "--file":
index += 1
inputFile = arguments[index]
default:
print("未知参数: \(arguments[index])")
}
index += 1
}
print("verbose=\(verbose), file=\(inputFile)")
// 注意:
// 最初はプログラムのパス
// 引数は CommandLine.arguments で取得
// 本番は ArgumentParser ライブラリを使用可

環境変数

環境変数を読み取る。設定と受け渡し。機微な情報に関する注意。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Foundation
// 環境変数の読み取り:
if let home = ProcessInfo.processInfo.environment["HOME"] {
print(home)
}
// 存在確認:
let hasKey = ProcessInfo.processInfo.environment["API_KEY"] != nil
// 全変数:
for (key, value) in ProcessInfo.processInfo.environment {
print("\(key)=\(value)")
}
// デフォルト値:
let port = ProcessInfo.processInfo.environment["PORT"] ?? "8080"
print(port)
// システム情報:
let os = ProcessInfo.processInfo.operatingSystemVersionString
let cores = ProcessInfo.processInfo.activeProcessorCount
print(os, cores)
// セキュリティ注意:
// 鍵は環境変数に置く
// コードベースにコミットしない
// 機密値の出力は避ける

Process

Process で子プロセスを起動する。コマンドの実行。出力の取得。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// コマンドの実行:
func run(_ cmd: String, _ args: [String]) throws -> String {
let process = Process()
process.executableURL = URL(fileURLWithPath: cmd)
process.arguments = args
// 出力のキャプチャ:
let pipe = Pipe()
process.standardOutput = pipe
try process.run()
process.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
return String(data: data, encoding: .utf8) ?? ""
}
// 使用:
try print(run("/usr/bin/env", ["date"]))
// 環境変数:
process.environment = ["PATH": "/usr/bin"]
// 作業ディレクトリ:
process.currentDirectoryURL = URL(fileURLWithPath: "/tmp")
// 終了コード:
let status = process.terminationStatus
// 注意:
// パスを正しく指定
// 長いコマンドは文字列で分割
// 非同期は terminationHandler を使用

パスの処理

URL のパス操作。結合、拡張子、ファイル名。サンドボックスのディレクトリ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Foundation
let fileURL = URL(fileURLWithPath: "/tmp/data/file.txt")
// ファイル名:
let name = fileURL.lastPathComponent // "file.txt"
// 拡張子を除く:
let base = fileURL.deletingPathExtension().lastPathComponent // "file"
// 拡張子:
let ext = fileURL.pathExtension // "txt"
// 親ディレクトリ:
let dir = fileURL.deletingLastPathComponent
// 結合:
let newFile = dir.appendingPathComponent("out.txt")
// 拡張子を追加:
let md = fileURL.appendingPathExtension("md")
// サンドボックスディレクトリ:
let docs = FileManager.default.urls(
for: .documentDirectory, in: .userDomainMask)[0]
let caches = FileManager.default.temporaryDirectory
// 判定基準:
// fileURL.hasDirectoryPath
// 相対パスを絶対へ:
// URL(fileURLWithPath: "a.txt").standardizedFileURL

終了コード

プログラムの終了コード。exit と fatalError。0 が成功、0 以外が失敗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// 正常終了:
exit(0)
// 失敗終了:
// exit(1)
// エラーメッセージ付き:
// 出力先は stderr
// FileHandle.standardError.write(
// Data("错误\n".utf8))
// exit(2)
// よく使う規約:
// 0 成功
// 1 一般的なエラー
// 2 使用方法エラー
// 127 コマンドが見つからない
// 子プロセスの状態を確認:
let process = Process()
// process.waitUntilExit()
// let code = process.terminationStatus
// print(code)
// 注意:
// exit は即座に終了
// defer は実行されない
// スクリプトで使用可
// 正常終了では exit を呼ばない
// Int を返す main 関数

シグナル処理

システムのシグナルを捕捉する。SIGINT など。安全な終了処理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Foundation
import Darwin
// シグナル処理:
signal(SIGINT) { _ in
print("收到 Ctrl-C")
exit(0)
}
// シグナルの無視:
// signal(SIGHUP, SIG_IGN)
// シグナルの一覧:
// SIGINT 割り込み (2)
// SIGTERM 終了 (15)
// SIGKILL 強制終了 (9) 捕捉不可
// グレースフル終了のパターン:
var running = true
signal(SIGTERM) { _ in
running = false
}
// while running {
// // メインループ...
// sleep(1)
// }
// 注意:
// シグナルハンドラは単純に
// アトミック変数で通信
// DispatchSource の方が現代向き:
// DispatchSource.makeSignalSource
// 異常終了は推奨しない

作業ディレクトリ

カレント作業ディレクトリの取得と変更。ファイルの相対パスの基準。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import Foundation
// 現在の作業ディレクトリ:
let cwd = FileManager.default.currentDirectoryPath
print(cwd)
// ディレクトリの切り替え:
// FileManager.default.changeCurrentDirectoryPath("/tmp")
// 相対パスの解決:
let rel = URL(fileURLWithPath: "data/config.json")
print(rel.path) // 現在のディレクトリ基準
// 正規化:
let normalized = rel.standardizedFileURL
// パスの判定:
let isAbsolute = rel.isFileURL
// よく使う場所:
let home = FileManager.default.homeDirectoryForCurrentUser
let temp = FileManager.default.temporaryDirectory
// 注意:
// 作業ディレクトリは相対パスに影響
// サンドボックス環境では異なる
// GUI アプリの注意:
// 作業ディレクトリがルートの場合も
// 絶対パスを明示的に構築すると安定
// 設定は相対基準で

プロセスのファイル I/O

標準入力の読み取り。標準出力への書き込み。パイプによるやり取り。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
// 標準入力の読み取り:
if let line = readLine() {
print("收到: \(line)")
}
// ループで読み取り:
// while let line = readLine() {
// print(line)
// }
// 標準出力:
print("输出到 stdout")
// 標準エラー:
FileHandle.standardError.write(
Data("错误信息\n".utf8))
// ファイルからのリダイレクト:
// swift main.swift < input.txt
// パイプ入力:
// echo "data" | swift main.swift
// 出力リダイレクト:
// swift main.swift > out.txt 2>&1
// 注意:
// readLine は EOF で nil を返す
// 大きな入力は性能に注意
// バイナリは FileHandle.standardInput

18.正規表現

正規表現リテラル、NSRegularExpression とパターンマッチング。

正規表現の基礎

正規表現の文法の概念。文字クラス、量指定子、グループ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Foundation
// 基本構文:
// リテラル:/abc/ は abc に一致
// 文字クラス:[abc] [0-9] \\d
// 量指定子:* + ? {n,m}
// アンカー:^ $ \\b
// グループ:(...) でキャプチャ
// または:a|b
// Swift 5.7+ の正規表現リテラル:
let pattern = /\\d{4}-\\d{2}-\\d{2}/
let text = "日期 2026-08-02 结束"
if let match = text.firstMatch(of: pattern) {
print(match.0) // 2026-08-02
}
// よく使う:
// \\d 数字 \\w 単語 \\s 空白
// \\D \\W \\S 逆
// 貪欲 vs 怠惰:
// a.*b 貪欲 / a.*?b 怠惰
// エスケープ:\\\\. はドットに一致

正規表現リテラル

Swift 5.7 の正規表現リテラル /.../。型付きのマッチ。名前付きキャプチャ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// 正規表現リテラル:
let pattern = /h(\\d+)x/
// 型付きキャプチャ:
let text = "h123x h99x"
if let match = text.firstMatch(of: pattern) {
print(match.0) // h123x
print(match.1) // 123 (Substring)
}
// 名前付きキャプチャ:
let named = /(?<code>\\d{3})-(?<area>\\d{4})/
if let m = "123-4567".firstMatch(of: named) {
print(m.code) // 123
print(m.area) // 4567
}
// オプション:
let caseInsensitive = /abc/ .ignoresCase()
// 全件検索:
for m in text.matches(of: pattern) {
print(m.0)
}
// 注意:
// 型安全でコンパイル時に検証
// NSRegularExpression と相互変換
// macOS 13 / iOS 16+ が必要

NSRegularExpression

NSRegularExpression は正規表現エンジン。範囲によるマッチ。古い OS との互換性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// 作成:
let pattern = "\\d+"
let regex = try! NSRegularExpression(pattern: pattern)
// マッチング:
let text = "价格 100 元,折扣 20 元"
let range = NSRange(text.startIndex..., in: text)
// 全マッチ:
let matches = regex.matches(in: text, range: range)
for m in matches {
if let r = Range(m.range, in: text) {
print(text[r])
}
}
// キャプチャグループ:
let groupRegex = try! NSRegularExpression(pattern: "(\\d+)-(\\d+)")
// 最初のマッチ:
if let first = groupRegex.firstMatch(in: text, range: range) {
// first.range(at: 1) は最初のグループ
}
// 置換:
let replaced = regex.stringByReplacingMatches(
in: text, range: range, withTemplate: "#")
print(replaced)
// オプション:
// .caseInsensitive / .anchorsMatchLines

マッチ

最初のマッチ、すべてのマッチ、マッチするかどうかの判定。範囲の取得。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// 一致するかどうか:
let email = "[email protected]"
// 完全一致:
let full = /[a-z]+@[a-z]+\\.[a-z]+/
print(email.wholeMatch(of: full) != nil)
// 部分一致:
print(email.contains(/@/))
// 最初のマッチ:
let pattern = /\\d+/
if let m = "abc123def".firstMatch(of: pattern) {
print(m.0) // 123
}
// 全マッチ:
let all = "a1b2c3".matches(of: /\\d+/)
print(all.count) // 3
// マッチ位置:
// m.range で範囲を返す
// 接頭辞マッチ:
if "2026-08".hasPrefix("2026") {}
// 注意:
// wholeMatch は完全一致
// firstMatch は最初
// matches は全部
// 空マッチに注意

キャプチャグループ

キャプチャグループの内容を取り出す。名前付きキャプチャ。正規表現のグループ参照。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Foundation
// キャプチャグループ:
let pattern = /(\\d{4})-(\\d{2})-(\\d{2})/
let text = "2026-08-02"
if let m = text.wholeMatch(of: pattern) {
print(m.0) // 2026-08-02
print(m.1) // 2026
print(m.2) // 08
print(m.3) // 02
}
// 名前付きキャプチャ:
let named = /(?<year>\\d{4})-(?<month>\\d{2})/
if let m = "2026-08".wholeMatch(of: named) {
print(m.year) // 2026
print(m.month) // 08
}
// オプショナルキャプチャ:
// (x)? で一致すると取得
// 非キャプチャグループ:
// (?:x) でキャプチャしない
// NSRegularExpression:
// m.range(at: 1) でグループ取得
// 用途:
// 構造化テキストの解析
// key=value の抽出

置換

正規表現でテキストを置換する。テンプレートからキャプチャグループを参照。条件付きの置換。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import Foundation
// リテラル置換:
let s = "Hello, world!"
let replaced = s.replacingOccurrences(of: "o", with: "0")
print(replaced) // Hell0, w0rld!
// 正規表現による置換:
let text = "日期: 2026-08-02"
let regex = try! NSRegularExpression(pattern: "(\\d{4})-(\\d{2})-(\\d{2})")
let nsRange = NSRange(text.startIndex..., in: text)
// テンプレートでグループ参照:
let formatted = regex.stringByReplacingMatches(
in: text, range: nsRange,
withTemplate: "$2/$3/$1")
print(formatted) // 日期: 08/02/2026
// 範囲の置換:
let trimmed = text.replacingOccurrences(
of: "\\s+", with: " ", options: .regularExpression)
// テンプレートの特殊文字:
// $0 全マッチ $1 グループ1
// 条件付き置換はループでマッチ

分割

正規表現で文字列を分割する。区切り文字の保持と削除。複数の区切り文字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Foundation
// 文字列の分割:
let csv = "a,b,c"
let parts = csv.split(separator: ",")
print(parts) // ["a", "b", "c"]
// 正規表現での分割:
let text = "one two three"
let words = text.components(separatedBy: .whitespacesAndNewlines)
// 複数の区切り文字:
let messy = "a;b,c|d"
let regex = try! NSRegularExpression(pattern: "[;,_|]+")
let splitParts = regex.split(messy)
// 空文字列を保持:
// split は既定で空を削除
// omittingEmptySubsequences
// 行で分割:
let lines = text.components(separatedBy: .newlines)
// 注意:
// 区切りは正規表現
// 分割時に区切り文字は除去
// 大きなテキストは性能に注意

よく使うパターン

メールアドレス、URL、数値、電話番号など、よく使う正規表現のテンプレート。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import Foundation
// メールアドレス:
let email = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/
// URL:
let url = /https?:\\/\\/[\\w.-]+(?:\\/[\\w._-]*)*/
// 整数:
let int = /^[+-]?\\d+$/
// 浮動小数点数:
let float = /^[+-]?\\d+\\.\\d+$/
// 携帯番号:
let phone = /^1[3-9]\\d{9}$/
// 身分証:
let idCard = /^\\d{17}[0-9Xx]$/
// IP アドレス:
let ip = /(?:\\d{1,3}\\.){3}\\d{1,3}/
// 中国語:
let chinese = /[\\u4e00-\\u9fa5]+/
// 空白:
let spaces = /\\s+/
// 使用:
print("[email protected]".wholeMatch(of: email) != nil)
print("13800138000".wholeMatch(of: phone) != nil)
// 注意:
// 正規表現はテストが必要
// 性能:複雑なパターンは慎重に
// 実際の検証は専用ライブラリを使う

19.ビルドとツール

swiftc によるコンパイル、SwiftPM によるビルド、フォーマットと検査のツール。

swiftc コンパイル

コマンドラインで単一ファイルをコンパイルする。実行ファイルの生成。最適化オプション。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 実行ファイルのコンパイル:
# swiftc main.swift -o app
# 実行:
# ./app
# 直接インタプリタ:
# swift main.swift
# 複数のソースファイル:
# swiftc main.swift utils.swift -o app
# モジュールの生成:
# swiftc -emit-module
# 最適化:
# -O 最適化コンパイル
# -Ounchecked より高速だがチェックなし
# -Onone デバッグ
# デバッグ情報:
# -g デバッグシンボルを生成
# フレームワークのリンク:
# -framework Foundation
# クロスコンパイル:
# -target x86_64-unknown-linux-gnu
# バージョンの確認:
# swift --version
# 説明:
# SwiftPM が複数ファイルを管理
# 単一ファイルは swiftc を使う

SwiftPM ビルド

swift build でプロジェクトをコンパイルする。インクリメンタルビルド。リリースモード。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# プロジェクトの初期化:
# swift package init --type executable
# ビルド:
# swift build
# リリースモード:
# swift build -c release
# プロダクトの指定:
# swift build --product app
# インクリメンタルビルド(既定)
# クリーン:
# swift package clean
# 依存関係の更新:
# swift package resolve
# 依存ツリーの確認:
# swift package show-dependencies
# 出力先:
# .build/debug/ と .build/release/
# 実行ファイルの実行:
# .build/debug/myapp
# 説明:
# Package.swift でパッケージを宣言
# 依存は git アドレス・バージョン
# ビルド成果物は .build/

テストの実行

swift test でテストスイートを実行する。XCTest とテストターゲット。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# テストの実行:
# swift test
# テストの指定:
# swift test --filter TestMath
# 単一のテストケース:
# swift test --filter TestMath/testAdd
# 並列:
# swift test --parallel
# コードカバレッジ:
# swift test --enable-code-coverage
# カバレッジレポートの生成:
# llvm-cov report .build/debug/*.xctest
# テストファイル:
import XCTest
final class TestMath: XCTestCase {
func testAdd() {
XCTAssertEqual(add(1, 2), 3)
}
}
# アサーション:
# XCTAssertTrue / XCTAssertNil
# 非同期テスト:
# XCTestExpectation
# 説明:
# テストターゲットは Package.swift で宣言
# 命名 testXxx で自動発見

Package.swift

SwiftPM のパッケージマニフェスト。ターゲットと依存関係の宣言。プラットフォームの設定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "MyApp",
// プラットフォーム:
platforms: [
.macOS(.v13)
],
// プロダクト:
products: [
.executable(name: "app", targets: ["App"]),
.library(name: "Core", targets: ["Core"]),
],
// 依存関係:
dependencies: [
.package(url: "https://github.com/xxx/yyy", from: "1.0.0"),
],
// ターゲット:
targets: [
.executableTarget(
name: "App",
dependencies: ["Core", "yyy"]),
.target(name: "Core"),
.testTarget(
name: "AppTests",
dependencies: ["App"]),
]
)
# 依存バージョン:
# from: 1.0.0 上位互換
# exact: 1.2.3 正確に指定
# branch: main

SwiftLint

コードスタイルの検査。ルールの設定。CI への統合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# インストール(Homebrew):
# brew install swiftlint
# 実行:
# swiftlint
# 自動修正:
# swiftlint autocorrect
# ディレクトリの指定:
# swiftlint lint --path Sources/
# 設定ファイル:
# デフォルト設定を生成
# swiftlint generate-docs
# よく使うルール:
# line_length 行の長さ
# trailing_whitespace 末尾の空白
# force_cast 強制キャスト
# force_unwrapping 強制アンラップ
# .swiftlint.yml の設定:
# disabled_rules:
# - force_cast
# opt_in_rules:
# - empty_count
# 無視ファイル:
# excluded:
# - Generated/
# CI 連携:
# 失敗するとビルド失敗
# スタイルの一貫性を保証

swift-format

公式のコードフォーマッタ。書式ルールと設定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# フォーマット:
# swift-format format -i Sources/*.swift
# チェック:
# swift-format lint -r Sources/
# 設定:
# swift-format dump-configuration > .swift-format
# Xcode との連携
# 設定例:
# {
# "indentation": {
# "spaces": 4
# },
# "lineBreakBeforeEachArgument": false,
# "rules": {
# "AllPublicDeclarationsHaveDocumentation": false
# }
# }
# 使い方:
# エディタ保存時にフォーマット
# pre-commit フック
# CI で書式チェック
# SwiftLint との違い:
# format は書式
# lint はスタイルルール
# 両方を併用できる

xcodebuild

Xcode プロジェクトをコマンドラインでビルドする。エクスポートとアーカイブ。CI での利用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# プロジェクト・ワークスペースの一覧:
# xcodebuild -list
# ビルド:
# xcodebuild -project App.xcodeproj \
# -scheme App -configuration Debug build
# ワークスペースのビルド:
# xcodebuild -workspace App.xcworkspace \
# -scheme App build
# 実機 SDK:
# -sdk iphoneos
# シミュレータ:
# -destination 'platform=iOS Simulator,name=iPhone 15'
# テスト:
# xcodebuild test -scheme App
# アーカイブ:
# xcodebuild archive -scheme App \
# -archivePath build/App.xcarchive
# IPA のエクスポート:
# xcodebuild -exportArchive \
# -exportOptionsPlist ExportOptions.plist
# CI でよく使う:
# 出力結果は xcresulttool で解析

リリースの流れ

バージョン管理、リリースビルド、配布。リリース前のチェックリスト。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# バージョン番号の規則:
# MAJOR.MINOR.PATCH
# 1.0.0 初期リリース
# 2.1.0 新機能
# 2.1.3 修正
# Git でタグ付け:
# git tag v2.1.0
# git push --tags
# リリース手順:
# 1. バージョン番号の更新
# 2. CHANGELOG の更新
# 3. すべてのテスト通過
# 4. リリース版のビルド
# 5. アーカイブの生成
# 6. 署名と公証
# 7. アップロードと配布
# コマンドラインツールの配布:
# 静的コンパイル
# tar でパッケージ化
# Homebrew formula
# チェックリスト:
# テストカバレッジ
# ドキュメント更新
# 依存関係の固定
# 成果物の検証

公式リンク

公式ドキュメントとリソースへの直接リンク。

このチートシートについて

このページは Swift 5.9 の自己完結型の速習ハンドブックで、言語のコアと最もよく使う標準ライブラリについて、実際のプロジェクトで現れる用法の約 80% をカバーする。内容は現代的な書き方に寄せている。値型(struct/enum)、プロトコル指向の設計、async/await による構造化並行処理、if let によるパターンマッチング、そして Codable によるシリアライズなどだ。信頼できる情報源としては Swift 公式の言語ガイドと API デザインガイドラインを参照するとよい。 19 の章がそれぞれ 1 つのテーマに集中している——最初のプログラムからオプショナル、プロトコル、並行処理、よくある落とし穴まで。各章は例付きの 8 つの小節(それぞれ 5〜20 行)に分かれ、全体で約 150 のトピックがある。コード片は意図的に短く、それ自体で意味が分かるようにしてある。 すべての処理はブラウザ内で完結する——アップロードもトラッキングもない。このページは GuruToolkit の無料開発者ツール集の一部であり、コード片はいかなる保証もなく自由に利用できる。

バージョン 2.1.0