【Swift】Swiftでカスタムデレゲートを作成する方法

:::note info
Swiftでプロトコルを定義し、Delegateを作成することで、Delegateパターンの使用方法を学びます。
:::

Delegate パターンは、Delegation とも呼ばれ、Swift コードや Apple ライブラリで頻繁に見られるアーキテクチャパターンです。この記事では、Delegation の概要と、Swift でカスタム delegate を作成する例を紹介します。

1. Swift の Delegate パターン
1. デリゲートプロトコルの作成
1. デリゲートプロトコルを拡張する
1. カスタムデレゲートの実装
1. カスタムデレゲートを使用する方法
1. オプションのデリゲートメソッドを作成する

## 1. Swift の Delegate パターン
Delegateという名前は、制御を委譲する（他の何かに制御を与える）という概念に由来しています。Swiftでは、デリゲートは、別のオブジェクトの動作を制御または修正するために使用できる、定義されたインターフェイスを持つコントローラオブジェクトです。

1つの例は、iOSアプリの <font color="#A276E4">UIApplicaitonDelegate</font> です。iOS システムは、<font color="#A276E4">UIApplicaitonDelegate</font> を使用して、プッシュ通知の受信、 <font color="#A276E4">URL</font> を開く、アプリケーションを起動するなどの iOS の動作を変更します。

## 2. デリゲートプロトコルの作成
カスタムデリゲートを作成するための最初のステップは、デリゲートプロトコルを作成することです。プロトコルは、カスタムデリゲートが実装しなければならないインターフェース、変数と関数のセットです。

```swift
// ViewActionDelegateというカスタムプロトコルを定義する
protocol ViewActionDelegate {
    // 期待されるデリゲート変数を定義する
    var state: ViewState { get }
    var userID: String? { get set }

    // 期待されるデリゲートブロックの定義
    var errorHandler: ((Error) -> Void)? { get set }

    // 期待されるデリゲート関数の定義
    func handle(action: ViewAction)
}
```

`ViewActionDelegate`の定義、および以下のコード例では、`ViewState`と`ViewAction`の列挙を使用しています。

```swift
enum ViewState {
    case `default`
    case loading
}

enum ViewAction {
    case save
    case cancel
}
```

## 3. デリゲートプロトコルを拡張する
デリゲートは、他の Swift の型と同様に、計算変数と関数を追加するために拡張することができます。

```swift
// ViewActionDelegate を拡張する
extension ViewActionDelegate {
    // 計算変数の定義
    var isReadyForNextAction: Bool {
        return state != .loading
    }
}
```

## 4. カスタムデレゲートの実装
次のステップは、カスタムデリゲート`ViewActionDelegate`に準拠したクラスまたは構造体を作成することです。まず、<font color="#C93E3F">**class**</font> や <font color="#C93E3F">**struct**</font> を定義し、その<font color="#C93E3F">**class**</font> や <font color="#C93E3F">**struct**</font> を拡張してデリゲートに対応させるというのがきれいなやり方です。

```swift
// ActionController クラスの定義
class ActionController {
    // デリゲートプロトコル定義に合致する変数を定義する
    var state: ViewState = .default
    var userID: String?
    var errorHandler: ((Error) -> Void)?
}
```

```swift
// ActionControllerを拡張し、デリゲートに準拠させる。
extension ActionController: ViewActionDelegate {
    // デリゲートプロトコル定義で要求される機能を実装する。
    func handle(action: ViewAction) {
        switch action {
        case .save:
            break
        case .cancel:
            break
        }
    }
}
```

## 5. カスタムデレゲートを使用する方法
最後に、カスタムデリゲートを使用します。この例では、`ActionView`という <font color="#A276E4">UIView</font> を作成し、`ViewActionDelegate`を使用して動作を変更します。

以下の例では、`ActionView`のオプションのデリゲート参照を使用していますが、オプションの参照は（メモリ管理の点から） <font color="#C93E3F">**weak**</font> です。

```swift
class ActionView: UIView {
    // デリゲートへの参照を保持するためのオプション変数を作成します。
    var delegate: ViewActionDelegate?

    // デリゲートのhandle(action:)の実装に基づいて、saveの動作を変更する。
    @IBAction func save() {
        guard let delegate = delegate else { return }
        guard delegate.isReadyForNextAction else { return }

        delegate.handle(action: .save)
    }

    // デリゲートの errorHandler に基づいて handle(error:) の動作を変更する。
    func handle(error: Error) {
        delegate?.errorHandler?(error)
    }
}
```

`ActionView`の`delegate`プロパティを使って動作を変更することで、カスタムデリゲートの実装は完了します。ここでは、`ActionController`をカスタマイズして、`ActionView`の`delegate`プロパティに割り当てている。

```swift
class ActionViewController: UIViewController {
    var actionView = ActionView()
    var actionController = ActionController()

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        // actionController の設定
        actionController.userID = "@robert"
        actionController.errorHandler = { (error) in
            // ハンドルエラー
        }

        // actionViewのデリゲートとしてactionControllerをアサインします。
        actionView.delegate = actionController
    }
}
```

## 6. オプションのデリゲートメソッドを作成する
Objective-C のデリゲートは、オプションのメソッドを持つことができます。Swift では、`@objc` 修飾子を使用して、プロトコルを Objective-C のプロトコルとしてマークすることができます。プロトコルを `@objc` としてマークした後、特定のプロトコル関数を `@objc` でマークすることができ、マークした関数上で`optional`修飾子を使用することができます。

```swift
// Swiftで@objcデリゲートを定義する
@objc protocol ViewActionDelegate {
    // オプションのデリゲート関数を作成する
    @objc optional func ignoreActions() -> Bool
}
```

オプションのデリゲートメソッドを実装し、アプリケーションで呼び出すことができます。

```swift
if delegate.ignoreActions?() == true {
    // ハンドルケース
}
```

## Swiftでカスタムデリゲートを実装する
以上です。デリゲーションを使用し、Swift でカスタムデリゲートを作成することで、オブジェクトの動作をカスタマイズし、アプリの複雑さをよりよく管理することができます。