Swift: Swiftプロトコル:インターフェース定��、プロトコル継承とデリゲートパターン

プロトコルはメソッド、プロパティ、その他の要件の設計図を定義します。これらはSwiftのプロトコル指向プログラミング(POP)の基礎です。このレッスンで��プロトコルの定義と準拠方法、そしてデリゲートパターンの実践をカバーします。

1. 学習目標


2. 決済開発者の実話

(1) 課題:各支払い方法が独自のインターフェースを持つ

Aliceのチームはクレジットカード、PayPal、WeChat Payの複数の支払い方法を��合する必要があります:

SWIFT
class CreditCardPayment {
    func processCardPayment(amount: Double, cardNumber: String) -> Bool {
        // Credit card processing
        return true
    }
}
class PayPalPayment {
    func payWithPayPal(amount: Double, email: String) -> Bool {
        // PayPal processing
        return true
    }
}
class WeChatPayment {
    func wechatPay(amount: Double, code: String) -> Bool {
        // WeChat Pay processing
        return true
    }
}

3つのクラス、3つの異なるメソッド名、3つの異なるパラメータリスト。 ビジネスロジックは支払いタイプのif-elseチェックで溢れています。支払い方法を1つ追加するにはすべての呼び出し箇所を変更する必要があります。

(2) 解決策:プロトコル

SWIFT
protocol PaymentMethod {
    func pay(amount: Double) -> Bool
}
struct CreditCard: PaymentMethod {
    let cardNumber: String
    func pay(amount: Double) -> Bool { /* Process */ true }
}
struct PayPal: PaymentMethod {
    let email: String
    func pay(amount: Double) -> Bool { /* Process */ true }
}
// Unified handling
func checkout(amount: Double, using method: PaymentMethod) {
    method.pay(amount: amount)
}

支払い方法が何かを気にする必要はありません — PaymentMethodに準拠していることだけが重要です。

(3) 利点:統一インターフェース、拡張可能

観点 独立した実装 プロトコル統一
メソッド名 すべて異なる pay(amount:) に統一
メソッドの追加 すべての呼び出し箇所を変更 準拠型を追加するだけ
テスト それぞれにテストスイートを書く プロトコルモックで統一テスト
結合度 高い 低い(プロトコル指向プログラミング)

3. プロトコルの定義と準拠

(1) プロトコル構文

100%
graph TB
    A[protocol キーワード] --> B[プロトコル名]
    B --> C[プロパティ要件]
    B --> D[メソッド要件]
    B --> E[サブスクリプト要件]
    C --> F["var name: String { get set }"]
    D --> G["func work()"]
    E --> H["subscript(...) -> Type"]
プロトコル要件 構文 備考
読み書きプロパティ { get set } var またはコンピューテッドプロパティ(get+set)
読み取り専用プロパティ { get } letvar、または読み取り専用コンピューテッドプロパティ
インスタンスメソッド func name() シグネチャのみ、実装なし
mutatingメソッド mutating func name() 値型がselfを変更可能

▶ サンプル: プロトコルの定義と準拠

SWIFT
// ============================================
// Defining and conforming to a protocol
// ============================================
// 1. Define the protocol
protocol Describable {
    var description: String { get }
    func summarize() -> String
}
// 2. Struct conforming to the protocol
struct Book: Describable {
    let title: String
    let author: String
    var description: String {
        return "\"\(title)\" by \(author)"
    }
    func summarize() -> String {
        return "Book: \(description)"
    }
}
// 3. Class conforming to the protocol
class Movie: Describable {
    let title: String
    let director: String
    init(title: String, director: String) {
        self.title = title
        self.director = director
    }
    var description: String {
        return "\(title) (directed by \(director))"
    }
    func summarize() -> String {
        return "Movie: \(description)"
    }
}
let book = Book(title: "1984", author: "George Orwell")
let movie = Movie(title: "Inception", director: "Christopher Nolan")
print(book.summarize())
print(movie.summarize())

出力:

TEXT 📖 参照専用
Book: "1984" by George Orwell
Movie: Inception (directed by Christopher Nolan)

4. プロトコル継承と合成

(1) プロトコル継承

プロトコルは1つ以上の他のプロトコルから継承できます:

100%
graph TB
    A[プロトコル: Payable] --> B["プロパティ: amount"]
    A --> C["メソッド: process()"]
    B --> D[プロトコル: Refundable]
    C --> D
    D --> E["メソッド: refund()"]
    D --> F[型: CreditCard]
    F --> G["Payable + Refundable を実装"]

▶ サンプル: プロトコル継承

SWIFT
// ============================================
// Protocol inheritance — a refundable payment protocol
// ============================================
protocol Payable {
    var amount: Double { get }
    func process() -> Bool
}
protocol Refundable: Payable {  // Inherits Payable
    func refund() -> Bool
}
// CreditCard implements refundable payment
struct CreditCard: Refundable {
    let amount: Double
    let cardNumber: String
    func process() -> Bool {
        print("Processing $\(amount) on card \(cardNumber)")
        return true
    }
    func refund() -> Bool {
        print("Refunding $\(amount) to card \(cardNumber)")
        return true
    }
}
// GiftCard can pay but not refund
struct GiftCard: Payable {
    let amount: Double
    let code: String
    func process() -> Bool {
        print("Processing $\(amount) with gift card \(code)")
        return true
    }
}
let payments: [Payable] = [
    CreditCard(amount: 100, cardNumber: "1234"),
    GiftCard(amount: 50, code: "GIFT-001")
]
for payment in payments {
    payment.process()
    // Check if also Refundable
    if let refundable = payment as? Refundable {
        print("  This payment can be refunded")
    }
}

出力:

TEXT 📖 参照専用
Processing $100.0 on card 1234
  This payment can be refunded
Processing $50.0 with gift card GIFT-001

(2) プロトコル合成

& 演算子を使って複数のプロトコルを結合し、型がそれらすべてに同時に準拠することを要求します:

SWIFT
protocol Identifiable {
    var id: String { get }
}
protocol Loggable {
    func log()
}
// Type must conform to both Identifiable and Loggable
func saveItem(_ item: Identifiable & Loggable) {
    print("Saving item \(item.id)")
    item.log()
}
struct User: Identifiable, Loggable {
    let id: String
    func log() { print("User logged: \(id)") }
}
let user = User(id: "U-001")
saveItem(user)

出力:

TEXT 📖 参照専用
Saving item U-001
User logged: U-001

5. extensionによるデフォルト実装とデリゲートパターン

(1) extensionによるデフォルト実装の提供

extension を使ってプロトコルメソッドにデフォルト実装を提供します — 準拠型は実装しないことを選択できます:

SWIFT
// ============================================
// Adding default implementations to protocols via extension
// ============================================
protocol Greetable {
    var name: String { get }
    func greet() -> String
}
// Default implementation
extension Greetable {
    func greet() -> String {
        return "Hello, \(name)!"
    }
}
// Person uses the default implementation
struct Person: Greetable {
    let name: String
    // No need to implement greet() — uses the default version
}
// Robot provides a custom implementation
struct Robot: Greetable {
    let name: String
    func greet() -> String {
        return "Beep boop, I am \(name)"
    }
}
print(Person(name: "Alice").greet())
print(Robot(name: "R2-D2").greet())

出力:

TEXT 📖 参照専用
Hello, Alice!
Beep boop, I am R2-D2

(2) デリゲートパターン

デリゲートパターンは、あるオブジェクトが自身の作業の一部をプロトコルに準拠した別のオブジェクトに委譲する設計パターンです:

100%
sequenceDiagram
    participant A as クラスA
    participant D as デリゲート (protocol)
    participant B as クラスB
    A->>D: イベント発生
    D->>B: デリゲートメソッドを呼び出す
    B-->>A: 処理結果を返す

▶ サンプル: デリゲートパターンの実装

SWIFT
// ============================================
// Delegate pattern: Download manager
// ============================================
// 1. Define the delegate protocol
protocol DownloadDelegate: AnyObject {
    func downloadDidStart(_ url: String)
    func downloadDidProgress(_ url: String, percent: Double)
    func downloadDidComplete(_ url: String, data: String)
    func downloadDidFail(_ url: String, error: String)
}
// 2. Download manager — delegates events to an external handler
class DownloadManager {
    weak var delegate: DownloadDelegate?
    func download(from url: String) {
        delegate?.downloadDidStart(url)
        // Simulate download progress
        for i in 1...5 {
            let percent = Double(i) / 5.0 * 100
            delegate?.downloadDidProgress(url, percent: percent)
        }
        // Simulate completion
        delegate?.downloadDidComplete(url, data: "Downloaded content from \(url)")
    }
}
// 3. View controller — acting as the delegate
class ViewController: DownloadDelegate {
    func downloadDidStart(_ url: String) {
        print("[UI] Download started: \(url)")
    }
    func downloadDidProgress(_ url: String, percent: Double) {
        print("[UI] \(url): \(Int(percent))%")
    }
    func downloadDidComplete(_ url: String, data: String) {
        print("[UI] Download complete: \(data.prefix(20))...")
    }
    func downloadDidFail(_ url: String, error: String) {
        print("[UI] Download failed: \(error)")
    }
}
let manager = DownloadManager()
let ui = ViewController()
manager.delegate = ui  // Set the delegate
manager.download(from: "https://example.com/file.zip")

出力:

TEXT 📖 参照専用
[UI] Download started: https://example.com/file.zip
[UI] https://example.com/file.zip: 20%
[UI] https://example.com/file.zip: 40%
[UI] https://example.com/file.zip: 60%
[UI] https://example.com/file.zip: 80%
[UI] https://example.com/file.zip: 100%
[UI] Download complete: Downloaded content...

ヒント: AnyObject 制約はデリゲートがクラス型であることを要求し(構造体ではない)、weak var を使って循環参照を回避できるようにします。これはデリゲートパターンの標準的な方法です。


6. 完全な例:設定可能なデータバリデーター

SWIFT
// ============================================
// Complete example: Data validation system
// Features: Protocol + protocol inheritance + extension defaults + delegate pattern
// ============================================
import Foundation
// 1. Validation protocol hierarchy
protocol Validatable {
    var value: Any { get }
    func validate() -> Bool
}
// Validation with detailed error reporting
protocol DetailedValidatable: Validatable {
    func errorMessage() -> String
}
// Default implementation
extension Validatable {
    func validate() -> Bool { return true }
}
// 2. Concrete validators
struct EmailValidator: DetailedValidatable {
    let value: Any
    func validate() -> Bool {
        guard let email = value as? String else { return false }
        return email.contains("@") && email.contains(".")
    }
    func errorMessage() -> String {
        return "Invalid email format"
    }
}
struct AgeValidator: DetailedValidatable {
    let value: Any
    func validate() -> Bool {
        guard let age = value as? Int else { return false }
        return age >= 18 && age <= 120
    }
    func errorMessage() -> String {
        return "Age must be between 18 and 120"
    }
}
struct NonEmptyValidator: Validatable {
    let value: Any
    // Uses default validate() which returns true, but we customize it
    func validate() -> Bool {
        guard let text = value as? String else { return false }
        return !text.isEmpty
    }
}
// 3. Delegate — validation result handler
protocol ValidationDelegate: AnyObject {
    func validationDidSucceed(for field: String)
    func validationDidFail(for field: String, error: String)
}
// 4. Validation manager
class ValidationManager {
    weak var delegate: ValidationDelegate?
    private var validators: [(field: String, validator: Validatable)] = []
    func addValidator(for field: String, validator: Validatable) {
        validators.append((field, validator))
    }
    func runAll() -> Bool {
        var allValid = true
        for (field, validator) in validators {
            if validator.validate() {
                delegate?.validationDidSucceed(for: field)
            } else {
                allValid = false
                let error = (validator as? DetailedValidatable)?.errorMessage() ?? "Validation failed"
                delegate?.validationDidFail(for: field, error: error)
            }
        }
        return allValid
    }
}
// 5. Usage
class FormController: ValidationDelegate {
    func validationDidSucceed(for field: String) {
        print("[OK] \(field) is valid")
    }
    func validationDidFail(for field: String, error: String) {
        print("[FAIL] \(field): \(error)")
    }
}
let manager = ValidationManager()
manager.delegate = FormController()
manager.addValidator(for: "email", validator: EmailValidator(value: "alice@example.com"))
manager.addValidator(for: "age", validator: AgeValidator(value: 25))
manager.addValidator(for: "name", validator: NonEmptyValidator(value: "Alice"))
let allValid = manager.runAll()
print("Form valid: \(allValid)")

出力:

TEXT 📖 参照専用
[OK] email is valid
[OK] age is valid
[OK] name is valid
Form valid: true

❓ よくある質問

Q プロトコルと基本クラスの違いは何ですか?
A プロトコルはインターフェースの設計図のみを定義します — プロパティを保存できません({ get set } の宣言のみ)。基本クラスはストアドプロパティとメソッド実装を提供できます。クラスは複数のプロトコルに準拠できますが、継承できる基本クラスは1つだけです。プロトコル指向プログラミング(POP)はSwiftの中核的な設��思想です。
Q プロトコルのデフォルト実装と基本クラスはいつ使い分けるべきですか?
A 共有ストアドプロパティや継承イニシャライザが必要な場合は基本クラスを使います。デフォルト実装付きの動作インターフェースを定義するだけで良い場合はプロトコルエクステンションを使います。プロトコルはより柔軟なので(複数プロトコル準拠可能)、優先してください。
Q デリゲートにweak varを使うのはなぜですか?
A 循環参照を避けるためです。DownloadManagerdelegate を保持し��delegateDownloadManager を保持すると、強参照サイクルが形成されます。weak はデリゲート参照が参照カウントを増やさないようにします。デリゲートにクラスを要求することで、構造体が弱参照をサポートしないため weak を使用できるようになります。
Q プロトコル内のmutatingメソッドの目的は何ですか?
A 準拠型自身のプロパティを変更できるメソッドをマークします。構造体と列挙型のメソッドはデフォルトではプロパティを変更できません — mutating を追加することで可能になります。クラスの実装では mutating を書く必要はありません — クラスメソッドはデフォルトでプロパティを変更できます。
Q @objcプロトコルは何に使われますか?
A @objc プロトコルは、そのプロトコルをObjective-Cコードから使用できるようにします。UIKitのデリゲートプロトコルでよく見られます。@objcプロトコルは optional 要件(実装が任意のメソッド)を持てます — 純粋なSwiftプロトコルにはない機能です。

📖 まとめ


📝 練習問題

  1. 基本: fly() メソッドを持つ Flyable プロトコルを定義してください。それに準拠し、fly() を実装する Bird 構造体と Airplane クラスを作成してください。
  2. 中級: Encryptable プロトコル(func encrypt(_: String) -> String)と Decryptable プロトコル(func decrypt(_: String) -> String)を定義してください。両方に準拠する CaesarCipher クラスを作成してください(シーザー暗号、シフト3)。���ロトコル合成 Encryptable & Decryptable をパラメータ型として使用してください。
  3. 発展: 「ソート可能データソース」デリゲートパターンを実装してください:SortableDataSource プロトコル(func numberOfItems() -> Intfunc item(at index: Int) -> String)と SortDelegate プロトコル(func didSort(items: [String]))を定義します。SortableDataSource に準拠する NameList クラスと SortDelegate に準拠する TableView クラスを作成してください。NameList はソート後に TableView に通知します。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%