えいむーさんは明日も頑張るよ

Delegateパターンを理解する

価格

Delegate とは

Delegate とはあるクラスからの処理の一部を別のクラスに委譲するための仕組みのこと。

何かをすることは決まっているけれど、何をするかは時と場合によって変更したいときに使えます。なお、ここで紹介しているコードは全て Playground 上で動作します。チェックに使ってみてください。

Delegate を定義しよう

基本的には Delegate はプロトコルで定義されます。変数や関数を定義しておくと良いです。

protocol SessionDelegate: AnyObject {
    func beginSession()
    func endSession()
}

今回はこんな感じで二つの関数を定義しました。

プロトコルにAnyObjectを適合させておくとweakをつけられるようになったりする上に、class でしか継承できなくなるので便利です。

子クラスの定義

次に、Delegate のプロパティを持つクラスを定義します。この場合、子クラスはSesseionDelegateのプロパティを持っているのでbeginSession()endSession()の二つの関数があることは知っているが、その中でどんな処理をするのかは知らない、という状態です。

で、その処理を親クラスに委譲しようというわけですね。

class Session {
    /// Delegateをメンバ変数に持たせる
    weak var delegate: SessionDelegate?

    init() {}
    init(delegate: SessionDelegate) {
        self.delegate = delegate
    }

    func start() {
        delegate?.beginSession()
        // 処理
        delegate?.endSession()
    }
}

ちなみにですが、delegate は必ずweakをつけなければいけません。そうしないとお互いがお互いを強参照してしまうので、どちらかのインスタンスが消えてももう一方が参照を持ち続けてしまい、循環参照に陥るためです。

親クラス

さて、最後に親クラスですが、ここは子クラスを継承した場合と、インスタンスとして持つ場合の二通りが考えられます。

個人的には継承するのが正しい使い方だと思うのですが、多分どちらもできます。

継承する場合

class Service: Session, SessionDelegate {
    func beginSession() {
        print("Delegate Session Start")
    }

    func endSession() {
        print("Delegate Session End")
    }

    override init() {
        super.init()
        self.delegate = self
    }
}

SessionSessionDelegateの二つを継承、適合し、super.init()実行後にself.delegate = selfで自分自身を Delegate として設定します。

let service: Service = Service()
service.start()

この状態で上のように Service クラスのインスタンスを作成して実行するとbeginSession()として Service クラスのbeginSession()が Session クラスのstart()内で呼ばれます。Session クラスは Service クラスの情報を全く知らない(子クラスなので)のに、不思議ですね。

これが Delegate というやつらしいです。

インスタンスとして持つ場合

class Service: SessionDelegate {
    func beginSession() {
        print("Delegate Session Start")
    }

    func endSession() {
        print("Delegate Session End")
    }

    let session: Session

    init() {
        self.session = Session()
        self.session.delegate = self
    }
}

基本は同じような感じです。

let service: Service = Service()
service.session.start()

Delegate で@escaping できるか

結論から言えば、もちろんできる。

protocol SessionDelegate: AnyObject {
    func beginSession()
    func endSession()
    func closure(completion: @escaping (String) -> Void)
}

例えば上のように定義すれば、親クラス側でclosure()の処理を決めた上で子クラスにStringを返すことができる。こんなの動くのかと思うかもしれないが、動く。

何故なら、子クラスのdelegateプロパティはSessionDelegateに準拠しているので、何かしらの処理が実行されたあとにString型が返ってくることを知っているからだ。

protocol SessionDelegate: AnyObject {
    func beginSession()
    func endSession()
    func closure(completion: (String) -> Void)
}

ちなみに@escapingを利用しない場合の書き方もできる。

価格
    えいむーさんは明日も頑張るよ © 2022