Swift: Swiftの入出力とデバッグ:print、アサーション、ブレークポイント
コードを書くのは仕事の半分です。このレッスンでは、print、アサーション、ブ��ークポイントを使ってコードのエラーを見つけて修正する方法��学びます。
1. 学習目標
- printの高度なパラメータ:separatorとterminator
- debugPrintとdumpの違いと使い分け
- assertアサーションとpreconditionチェック
- 文字列のフォーマットと整列出力
- Playgroundのライブプレビューとブレークポイントデバッグの基本
2. 実話:独学の初心者
(1) 課題:論理的に正しいコードなのに結果が間違っている
MikeはSwiftを独学する初心者で、平均点を計算するプログラムに取り組んでいます:
let scores = [85, 92, 78, 90, 88]
var total = 0
for i in 0...5 {
total += scores[i]
}
let average = total / 5
print("Average: \(average)")
プログラムは即座にクラッシュしました。Mikeは理由がわかりませんでした。彼は30分間ネットで検索し、様々な修正を試みました。修正のたびに再実行が必要でしたが、それでも問題を特定できませんでした。
(2) 解決策:printベースのデバッグ
友人がprintを使って中間変数を出力して問題箇所を特定することを勧めました:
let scores = [85, 92, 78, 90, 88]
print("Array length: \(scores.count)") // Output: 5
var total = 0
for i in 0..<scores.count { // Use count instead of hardcoding
print("Processing index \(i): value = \(scores[i])")
total += scores[i]
}
print("Total: \(total)")
let average = total / 5
print("Average: \(average)")
出力:
TEXT 📖 参照専用Array length: 5 Processing index 0: value = 85 Processing index 1: value = 92 Processing index 2: value = 78 Processing index 3: value = 90 Processing index 4: value = 88 Total: 433 Average: 86
中間変数を出力することで、Mikeは配列のインデックスが0-4であるのに、0...5(インデックス5を含む)を使っていたことを即座に発見しました。
(3) 結果:printを習得した後のデバッグ効率
| 観点 | Before(推測) | After(printデバッグ) |
|---|---|---|
| 問題特定までの時間 | 30分以上 | 1〜2分 |
| 修正の正確さ | 50%(しばしば間違い) | 95% |
| コードの理解 | 「なぜクラッシュしたかわからない」 | 「各ステップの状態がわかる」 |
| 新��い問題を解決する自信 | 3/10 | 8/10 |
3. printの高度な使い方
printはSwiftで最もよく使われるデバッグツールですが、そのパラメータは値を出力するだけではありません。
graph TB
A[print関数] --> B["items: 出力する値"]
A --> C["separator: 区切り文字"]
A --> D["terminator: 終端文字"]
A --> E["to: 出力先"]
B --> F[カンマ区切りで複数の値]
C --> G["デフォルト: スペース"]
C --> H["カスタム: | や , など"]
D --> I["デフォルト: 改行"]
D --> J["カスタム: 空文字列"]
| パラメータ | 型 | デフォルト | 目的 |
|---|---|---|---|
items |
Any... |
必須 | 出力する内容 |
separator |
String |
" " |
複数項目間の区切り文字 |
terminator |
String |
"\n" |
末尾の改行文字 |
to |
TextOutputStream |
nil |
出力先(デフォルト: コンソール) |
(1) separatorとterminator
// Default: space-separated, newline-terminated
print("Hello", "Swift", "World")
// Hello Swift World
// Custom separator
print("Hello", "Swift", "World", separator: ", ")
// Hello, Swift, World
// Custom terminator (no newline)
print("Loading", terminator: "...")
print("Done")
// Loading...Done
// Combined usage
print("A", "B", "C", separator: " | ", terminator: ".\n")
// A | B | C.
(2) debugPrintとdump
debugPrintはデバッグ情報(引用符と型情報付き)を出力し、dumpは詳細な構造を出力します:
let name = "Alice"
let numbers = [1, 2, 3]
print(name) // Alice
debugPrint(name) // "Alice"
dump(name) // - "Alice"
print(numbers) // [1, 2, 3]
debugPrint(numbers) // [1, 2, 3]
dump(numbers)
// ▿ 3 elements
// - 0 : 1
// - 1 : 2
// - 2 : 3
| 関数 | 目的 | 文字列出力 | 配列出力 |
|---|---|---|---|
print |
通常出力 | Alice | [1, 2, 3] |
debugPrint |
デバッグ出力(型情報を表示) | "Alice" | [1, 2, 3] |
dump |
詳細構造出力 | - "Alice" | 1要素ずつ表示 |
▶ サンプル: 整形されたログ出力
// ============================================
// Simulate system log output
// Demonstrates advanced print parameters and debugPrint
// ============================================
let event = "USER_LOGIN"
let user = "Alice"
let statusCode = 200
let duration = 0.045
// 1. Use separator to format the log
print("[\(event)]", user, "Status: \(statusCode)", separator: " | ", terminator: "")
print(" (\(duration)s)")
// [USER_LOGIN] | Alice | Status: 200 (0.045s)
// 2. Output tabular data
print()
print("=== Report ===")
print("Item", "Price", "Qty", separator: " | ")
print("-----", "-----", "---", separator: " | ")
print("Book", "12.99", "3", separator: " | ")
print("Pen", "1.50", "10", separator: " | ")
print("Bag", "49.99", "1", separator: " | ")
// 3. debugPrint for development debugging
let input: String? = "test"
debugPrint("Debug: input = \(input)")
// "Debug: input = Optional(\"test\")"
出力:
TEXT 📖 参照専用[USER_LOGIN] | Alice | ステータス: 200 (0.045s) === レポート === 商品 | 価格 | 数量 ----- | ----- | --- 本 | 12.99 | 3 ペン | 1.50 | 10 バッグ | 49.99 | 1 デバッグ: input = Optional("test")
4. アサーションとプレコンディション
アサーションとプレコンディションはSwiftの組み込み防御的プログラミ���グツールで、開発中にロジックエラーを早期に発見します。
graph LR
A[実行時チェック] --> B[assert]
A --> C[precondition]
B --> D[デバッグモードのみ]
B --> E[開発中の問題を発見]
C --> F[デバッグ + リリース]
C --> G[回復不能なエラー]
| 関数 | 有効モード | 目的 | 例 |
|---|---|---|---|
assert |
デバッグのみ | 開発中の内部整合性チェック | assert(age > 0) |
assertionFailure |
デバッグのみ | 無条件アサーション発動 | assertionFailure("ここに到達すべきではない") |
precondition |
全モード | 事前条件チェック | precondition(!name.isEmpty) |
preconditionFailure |
全モード | 無条件終了 | preconditionFailure("致命的エラー") |
(1) assert デバッグアサーション
assertはデバッグモードでのみ有効で、リリースモードでは削除されるため、パフォーマンスへの影響はありません:
func calculateDiscount(price: Double, percent: Double) -> Double {
assert(price > 0, "Price must be greater than 0")
assert(percent >= 0 && percent <= 100, "Discount must be between 0-100")
let discount = price * percent / 100.0
return price - discount
}
let finalPrice = calculateDiscount(price: 100.0, percent: 20)
print(finalPrice) // 80.0
// The following would trigger assertion failures in Debug mode:
// calculateDiscount(price: -10, percent: 20) // ❌ assert fails
// calculateDiscount(price: 100, percent: 150) // ❌ assert fails
(2) preconditionチェック
preconditionはデバッグモードとリリースモードの両方で有効で、条件が満たせない場合に��ログラムを即座に終了します:
func sendEmail(to address: String, message: String) {
precondition(address.contains("@"), "Invalid email address: \(address)")
precondition(!message.isEmpty, "Message cannot be empty")
print("Sending email to \(address): \(message)")
}
sendEmail(to: "alice@example.com", message: "Hello!")
// Sending email to alice@example.com: Hello!
// The following would trigger precondition failures (all modes):
// sendEmail(to: "invalid", message: "Hi")
▶ サンプル: パラメータ検証
// ============================================
// User registration parameter checks
// Demonstrates assert and precondition usage
// ============================================
import Foundation
func registerUser(name: String, age: Int, email: String) {
// precondition: Public API contract (active in all modes)
precondition(name.count >= 2, "Username must be at least 2 characters")
precondition(age >= 18, "User must be at least 18 years old")
precondition(email.contains("@"), "Invalid email format")
// assert: Internal logic check (Debug only)
let emailParts = email.split(separator: "@")
assert(emailParts.count == 2, "Email should contain exactly one @ symbol")
let domain = String(emailParts[1])
assert(domain.contains("."), "Email domain is invalid")
// Actual registration logic
print("Registration successful: \(name), age \(age)")
print("Confirmation email sent to: \(email)")
}
// Valid call
registerUser(name: "Alice", age: 28, email: "alice@example.com")
print("---")
// Calls that would trigger precondition failures (commented out to avoid crashing)
// registerUser(name: "A", age: 20, email: "test@test.com")
出力:
TEXT 📖 参照専用Registration successful: Alice, age 28 Confirmation email sent to: alice@example.com ---
5. Playgroundデバッグ
Playgroundsはprintよりも強力なデバッグ機能を提供し、ライブプレビューとブレークポイントを含みます。
(1) Playgroundライブプレビュー
Playgroundsのサイドバーには各行の結果がリアルタイムで表示されます:
// Run in Playground — the sidebar shows each step's result
let name = "Alice" // "Alice"
var score = 0 // 0
score += 85 // 85
score += 92 // 177
let average = score / 2 // 88
graph TB
A[Playgroundデバッグ] --> B[ライブ結果パネル]
A --> C[ブレークポイントデバッグ]
A --> D[値の履歴]
B --> E[行ごとに結果を表示]
C --> F[一時停止 / ステップ実行 / 続行]
D --> G[変数値の変化曲線]
| 機能 | 使用方法 | 目的 |
|---|---|---|
| ライブプレビュー | コード編集時に自動で右側に結果表示 | 各ステップの結果を素早く確認 |
| ブレークポイント | 行番号をクリックして追加 | 実行を一時停止し、ステップ実行で追跡 |
| 値の履歴 | 変数にマウスオーバー | 変数が時間とともにどう変化するか確認 |
| 式のプレビュー | コードを選択 | 選択した式を素早く評価 |
(2) ブレークポイントデバッグ
行番号の左側をクリックしてブレークポイ��トを設定します。プログラムがその行に到達すると一時停止し、現在のすべての変数値を検査できます:
func calculateTotal(items: [Double], tax: Double) -> Double {
var subtotal = 0.0
// Set a breakpoint on this line
for item in items {
subtotal += item
}
let taxAmount = subtotal * tax
let total = subtotal + taxAmount
return total
}
let cart = [29.99, 49.99, 15.00]
let final = calculateTotal(items: cart, tax: 0.08)
print("Total: $\(final)")
▶ サンプル: Playgroundデバッグの実践
// ============================================
// Practice Playground debugging techniques
// Paste this code into a Playground and run it
// ============================================
import Foundation
// 1. Set a breakpoint on the line below to observe variable values
let data: [String: Any] = [
"product": "Swift Book",
"price": 39.99,
"quantity": 3,
"inStock": true
]
// 2. Step through the following code
let productName = data["product"] as? String ?? "Unknown"
let price = data["price"] as? Double ?? 0.0
let quantity = data["quantity"] as? Int ?? 0
let inStock = data["inStock"] as? Bool ?? false
print("Product: \(productName)")
print("Price: $\(price)")
print("Quantity: \(quantity)")
print("In Stock: \(inStock)")
// 3. Observe the conditional check results
if inStock {
let totalCost = price * Double(quantity)
print("Total Cost: $\(totalCost)")
} else {
print("Item is out of stock")
}
// 4. Use dump to inspect complex data
print("\n=== Debug Info ===")
dump(data)
出力:
TEXT 📖 参照専用商品: Swift Book 価格: $39.99 数量: 3 在庫あり: true 合計コスト: $119.97 === デバッグ情報 === ▿ 4 key/value pairs ▿ (2 elements) - key: "product" - value: "Swift Book" ▿ (2 elements) - key: "price" - value: 39.99 ▿ (2 elements) - key: "quantity" - value: 3 ▿ (2 elements) - key: "inStock" - value: true
6. 完全な例:成績分析デバッグツール
// ============================================
// Grade analysis tool
// Combines print debugging + assertion checks + formatted output
// ============================================
import Foundation
// 1. Student grade data
let studentName = "Alice"
let scores = [85.0, 92.0, 78.0, 90.0, 88.0]
// 2. Debug assertions to validate data
assert(scores.count > 0, "Score list cannot be empty")
for score in scores {
assert(score >= 0 && score <= 100, "Score must be between 0-100: \(score)")
}
// 3. Calculate statistics
var total = 0.0
var highest = scores[0]
var lowest = scores[0]
// Set a breakpoint here to observe the loop process
for (index, score) in scores.enumerated() {
print("[DEBUG] Index \(index): \(score)")
total += score
if score > highest { highest = score }
if score < lowest { lowest = score }
}
let average = total / Double(scores.count)
// 4. Formatted output
print(String(repeating: "=", count: 35))
print("Student: \(studentName)")
print(String(repeating: "-", count: 35))
print("Subject", "Score", "Grade", separator: " | ")
print(String(repeating: "-", count: 35))
for (index, score) in scores.enumerated() {
let grade = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : "D"
print("Subject \(index + 1)", score, grade, separator: " | ")
}
print(String(repeating: "-", count: 35))
print("Total: \(total)")
print("Average: \(String(format: "%.1f", average))")
print("Highest: \(highest)")
print("Lowest: \(lowest)")
print(String(repeating: "=", count: 35))
// 5. Precondition to ensure reasonable results
precondition(average >= 0 && average <= 100, "Average is outside expected range")
precondition(highest >= lowest, "Highest score should not be lower than lowest")
出力:
TEXT 📖 参照専用[DEBUG] Index 0: 85.0 [DEBUG] Index 1: 92.0 [DEBUG] Index 2: 78.0 [DEBUG] Index 3: 90.0 [DEBUG] Index 4: 88.0 =================================== Student: Alice ----------------------------------- Subject | Score | Grade ----------------------------------- Subject 1 | 85.0 | B Subject 2 | 92.0 | A Subject 3 | 78.0 | C Subject 4 | 90.0 | A Subject 5 | 88.0 | B ----------------------------------- Total: 433.0 Average: 86.6 Highest: 92.0 Lowest: 78.0 ===================================
❓ よくある質問
printは人間が読みやすい形式(例:Alice)で出力し、debugPrintはデバッグ向け形式(例:引用符付きで"Alice")で出力します。カスタム型はCustomStringConvertibleとCustomDebugStringConvertibleプロトコルを実装して両方の出力を制御できます。assertはデバッグモード(-Onone)でのみ有効です。リリースモードでは、コンパイラはassertの評価と実行をスキップします。したがって、assertの内部に副作用のあるロジックを決して置かないでください。preconditionは全モードで有効で、回復不能な致命的エラーに使用します。例:配列境界チェック前、必須パラメータがnil、論理的に決して到達しないはずの分岐。公開APIのパラメータ検証にはpreconditionを使用します。📖 まとめ
printのseparatorは項目間の区切り文字をカスタマイズし、terminatorは終端文字を制御するdebugPrintは型情報付きの出力を表示し、dumpは詳細な構造を表示するassertはデバッグモードで内部ロジックの整合性をチェックす���preconditionは全モードで回復不能な事前条件をチェックする- Playgroundのサイドバーには各行の結果がリアルタイムで表示される
- ブレークポイントデバッグによりコードを1行ずつ実行し、変数の変化を観察できる
📝 練習問題
- 初級:
printを使って簡単な九九の表(1-3)を生成し、separatorとterminatorで形式を制御して表スタイルで出力してください。 - 中級: 関数
divide(_ a: Double, by b: Double) -> Doubleを作成し、preconditionで除数が0でないことをチェックし、assertで結果が妥当な範囲内かを確認してください。そしてprintで整形して結果を出力してください。 - 上級: ATM出金プログラムをシミュレートしてください。
assertで出金額が100の倍数であることを確認し、preconditionで残高が十分であることをチェックします。printのseparatorとterminatorを使って、時間、金額、残高を含む取引詳細を整形して表示してください。