📚

【Swift】delegate実装の流れ

2021/10/03に公開
2

備忘録としてdelegateについてまとめます。

登場人物

プロトコル(今回の例:Protocol.swift)

delegateで使用するメソッドやプロパティを定義しておく。
メソッドは定義するだけで実装はしない。
プロトコルで定義されたメソッドはデリゲートメソッドと呼ばれる。
※プロトコルの記事を書いたらリンクを貼ります。

処理を任せる側(今回の例:Person.swift)

処理を任せる相手を保持するプロパティ(プロパティ名はdelegateがよく使われる?)を持つ。
処理を任せる相手が決まったら、どの条件でどのような処理を行うのかなど処理の流れを実装する。例えば、処理を任せる相手によって実行する処理を変えたりなど。

処理を任される側(今回の例:John.swift、Jack.swift)

プロトコルを適合する。
プロトコルで定義されたデリゲートメソッドの処理内容を実装する。
任される側(John.swift 、Jack.swift)で処理を実装するため、任せる側(Person.swift)ではデリゲートメソッドの内容を実装しなくて済む。

実装例

プロトコル(今回の例:Protocol.swift)

Protocol.swift
//
//  Protocol.swift

protocol GreetingDelegate {
    
    func sayHello()
    func sayName()
    func sayAge()
}

extension GreetingDelegate {
 
    // デフォルト実装
    // プロトコル適合先で実装しなくてもエラーにならなくなる
    func sayAge() {
        print("My age is 10")
    }
}

デリゲートメソッド
・sayHello()
・sayOwnName()
・sayAge()
を定義しています。
また、sayAge()メソッドはextensionでデフォルト実装をしています。デフォルト実装をすることでプロトコルを適合したクラスでsayAge()メソッドの処理を実装しなくても怒られなくて済みます。実装せずにsayAge()メソッドを呼び出した場合は、デフォルト実装が実行されます。

処理を任せる側(今回の例:Person.swift)

Person.swift
//
//  Person.swift

final class Person {
    
    var delegate: greetingDelegate?  // 処理を任せる相手を保持する
    
    func greet() {
        guard let delegate = delegate else {
            // 処理を任せる相手が決まっていない場合
            print("No Person")
            return
        }
        if type(of: delegate) == John.self {
            // 処理を任せる相手がJohnクラスの場合
            // 挨拶と名前をログに出力
            delegate.sayHello()
            delegate.sayName()
        } else if type(of: delegate) == Jack.self {
            // 処理を任せる相手がJackクラスの場合
            // 挨拶と名前と年齢をログに出力
            delegate.sayHello()
            delegate.sayName()
            delegate.sayAge()
        }
        
    }
}

処理を任せる側のクラスでは、どういう条件でどの処理を行うのかといった処理の流れを記述しますが、sayHello()などメソッドの具体的な処理内容は記述しません。
また、以下の2点が保証されているため気にする必要がない。
 ・delegateに指定したクラスでデリゲートメソッドが実装されていること。
 ・デリゲートメソッドの処理内容(この場合、print("Hello!"))が変更になった
  としてもdelegate.sayHello()を書き換える必要がない。John.swiftやJack.swift
  内でのみ変更を加えれば良い。

処理を任される側(今回の例:John.swift、Jack.swift)

John.swift
//
//  John.swift
final class John: GreetingDelegate {
    
    func sayHello() {
        print("Hello!")
    }
    
    func sayName() {
        print("My Name is John")
    }
}
Jack.swift
//
//  Jack.swift
final class Jack: GreetingDelegate {
    
    func sayHello() {
        print("Hello!")
    }
    
    func sayName() {
        print("My Name is Jack")
    }
    
    func sayAge() {
        // デフォルト実装を上書き
        print("My age is 25")
    }
}

処理を任される側(John.swift、Jack.swift)では、デリゲートメソッドの具体的な処理内容を実装します。ここでは、デフォルト実装の上書きも可能です。

実際に動かしてみる

◯処理を任せる相手を指定しなかった場合

GreetingViewController.swift
//
//  GreetingViewController.swift
import UIKit

class GreetingViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let person = Person()  // 処理を任せるクラス
        let john = John()      // 処理を任されるクラス
        
        person.greet()
    }
}
出力結果
No Person

◯John.swiftに処理を任せた場合

GreetingViewController.swift
//
//  GreetingViewController.swift
import UIKit

class GreetingViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let person = Person()  // 処理を任せるクラス
        let john = John()      // 処理を任されるクラス
        
        person.delegate = john  // 処理を任せる相手を指定する
        
        person.greet()
    }
}
出力結果
Hello!
My Name is John

◯Jack.swiftに処理を任せた場合

GreetingViewController.swift
//
//  GreetingViewController.swift
import UIKit

class GreetingViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let person = Person()  // 処理を任せるクラス
        let jack = Jack()      // 処理を任されるクラス
        
        person.delegate = jack  // 処理を任せる相手を指定する
        
        person.greet()
    }
}
出力結果
Hello!
My Name is Jack
My age is 25

期待通りの結果が得られました!

ご指摘などありましたら、よろしくお願いします。

delegateを使う時は(2020/06/24追記)

ちなみに記事では細かいことは省きましたが、実際にdelegateを使う時は循環参照を避けるために weak をつけた方が良いです。

weak var delegate: greetingDelegate?

この辺がわかりやすくまとめられています。
delegateの循環参照について①
delegateの循環参照について②

Discussion

えびえび

わかりやすい記事ありがとうございます!大変参考になりました。
1点だけ。SwiftのAPIデザインガイドライン中でプロトコルはアッパーキャメルケースが望ましいとされていますが、greetingDelegate はロワーキャメルケースになっていることが気になりました。

sh0sh0

コメントありがとうございます!
これはtypoですね...
修正しました!