LoginSignup
0
2

More than 5 years have passed since last update.

ジェネリクスを使ってDelegateの振る舞いを変更する

Last updated at Posted at 2017-02-03

IOS開発を行うと、多くの場合Delegateパターンを利用して開発を行うことになると思います。

例えば、WebAPIリクエストを行う処理を考えてみましょう。

// Delegate用Protocol
protocol RequestDelegate : class {
    func begin()
    func done()
}

// APIリクエストクラス
class Request {
    weak var delegate: RequestDelegate?

    func call() {
        delegate?.begin()
        delegate?.done()
    }
}

// APIリクエストを行うViewController
class MyViewController: UIViewController, RequestDelegate {
    private var request: Request?

    override func viewDidLoad() {
        super.viewDidLoad()

        // リクエストクラスをメンバ変数に保持して、Delegate先に自身を設定
        request = Request()
        request.delegate = self
    }

    func apiCall() {
        requst.call()
    }
}

// Delegate処理の実装
extension RequestDelegate {
    func begin() {
        print("request begin.")
    }

    func done() {
        print("request done.")
    }
}

さて、開発を進めていく中でこのViewControllerが管理する画面とほとんどが同じだがAPIの呼び出し前後の処理だけ変更した新しい画面が必要になったとします。

この時、真っ先に対応策として思いつくのは画面ごとのフラグを用意してAPIの呼び出し前後の処理に条件分岐を与えることではないでしょうか?

  class MyViewController: UIViewController {
+     private(set) var originView: Bool = true
        .
        .
        .
  extension MyViewController: RequestDelegate {
      func begin() {
-         print("request begin.")
+         originView ? print("request begin.") : print("new begin.")
      }

      func done() {
-         print("request done.")
+         originView ? print("request done.") : print("new done.")
      }
  }

条件分岐が出来てしまったので、バグがないことを確認するためにもテストコードを書いておかないとなりませんね。

でも、Swiftのジェネリクスを利用すればこの程度の改修は型チェックのみでなんとかできてしまうのです。

まずは、MyViewControllerを改修します。

- class MyViewController: UIViewController, RequestDelegate {
+ class MyViewController<T>: UIViewController, RequestDelegate {

MyViewControllerにジェネリクスをもたせました。

次にジェネリクスに適用させたい適当な型を定義しておきます。

新しい画面の追加なので、NewMyViewとしておきましょう。

// 実装はいりません
class NewMyView {}

さて、ここまで出来たら新しい画面のためのエクステンションを作れば完了です。

// 新しい画面のためのDelegate処理の実装
extension RequestDelegate where Self: MyViewController<NewMyView> {
    func begin() {
        print("new begin.")
    }

    func done() {
        print("new done.")
    }
}

いかのようにして利用することで、新しい画面用の処理と、これまでの画面の処理が分離出来ていることが確認出来ます。

let originVc = MyViewController<Any>()
originVc.apiCall() // request begin. request done.

let newVc = MyViewController<NewMyView>()
newVc.apiCall() // new begin. new done.

通常、Delegateパターンを利用したextensionは、対象のクラスにDelegateProtocolを適合させて拡張します。

extension MyViewController: RequestDelegate {
    .
    .
    .
}

しかし、このように定義した場合制約と継承は同時に行えないというジェネリクスの性質に引っかかってしまうため、DelegateProtocol側に、誰に拡張を与えるか?という方針を取らせるようにしています。

今回テーマにしたケースの解決策としてはViewControllerをもう一つ作るというのもありますが、ほぼ同じ性質のクラスを複数用意するのは面倒ですよね?

クラスのサブタイプにジェネリクスを持たせておくと、色々と小回りが利いて便利ですね。


今回のソースコードの完成系はGistに置いてあります。
https://gist.github.com/k-motoyan/888832d3ce23767143951c2f3ad860db

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2