Swift デリゲートが分からないので調べまくった過程をメモ


iPhoneアプリ作ってみたいと思って入門書籍を買ってみたはいいけど
どの書籍も入門者を置いてけぼりにしているなと感じたところが沢山あり、その時、特になんだこれ?と思ったのがデリゲートでした。
こんなのいきなり出てきても分からねーよ・・と。
一応、デリゲートについて書籍でも解説されているのですが簡単にしか説明されていないのでやはり分からない。
結局、ネットで検索して調べることになるんですが
これがまた分かりにくい、いろんなサイトで分かりやすく説明しているように見えて
これあってるの?というサイトがけっこうあって最終的にどれが正しいの?という状態に。
なので、この記事は参考程度にしてください。確実に内容が正しいと保証するものではありません。
調べていった上でこれは!と思ったサイトの記事を参考にしてメモしています。

まず、はじめにぶち当たったこと
各サイトで使われている用語
移譲元、移譲先、移譲する、通知を受ける、問い合わせる、処理を任せるクラス、処理を任せられるクラス、代理人
簡単に説明しようとしていて返ってわかりにくくなっている感じで、イラストとか入れているサイトとかもあって余計わかりにくい。
分かりやすくしようとしないでいいのできちんとした専門用語で説明してもらった方がいいのではないかと度々思いました。
結果的には
https://ja.stackoverflow.com/questions/33023/swift-%E3%83%87%E3%83%AA%E3%82%B2%E3%83%BC%E3%83%88%E3%81%AE%E9%80%9A%E7%9F%A5%E3%81%AE%E9%81%8E%E7%A8%8B/33026
で、かなりすっきりしたのですが
最後に上記については書いてみます。

そこに行き着くまでの過程として
何十サイトかを見てとりあえず理屈が分かったのが
http://programming-beginner-memo.com/?p=367
https://qiita.com/s_emoto/items/04505ed549178555b10b

上記を参考にデリゲートを実装するとこんな感じ。
アバターの情報を出力するやつ

プロトコル
型のインターフェースを定義するもの。
デリゲートの場合だと処理を任せられるクラスで実装しなければいけないメソッド

// Protocol.swift

protocol avatarDelegate {

    func setEyeSize() // 目の大きさ
    func setHairLength() // 髪の長さ
    func setHairColor() // 髪の色
    func setSkinColor() // 肌の色
    func setFaceContour() // 顔の形
    func setHeight() // 身長

}

extensionはクラスの拡張のために使われます。
今回の場合デフォルト実装したいメソッドを記述
これを設定しておくと移譲先のクラスでメソッドを書かなくてもextensionで記述しているメソッドに関してはデフォルトで実装される。

extension avatarDelegate {

    // デフォルト実装
    // プロトコルに準拠するクラスで実装しなくてもエラーにならない。初期メソッドみたいな感じ
    func setSkinColor() {
        print("肌の色:ベージュ")
    }
    func setHairLength() {
    	print("髪の長さ:ロング")
    }
    func setHairColor() {
    	print("髪の色:黒")
    }
}

処理を任せられるクラス(移譲先)
avatarDelegateに準拠してメソッドを記述
extensionで設定しているメソッドに関しては記述しなくてもok。記述しない場合はデフォ実装となる

//  Takuya.swift
final class Takuya: avatarDelegate {
	
    func setEyeSize() {
        print("目の大きさ:大きい")
    }
    func setHairLength() {
        print("髪の長さ:ロング")
    }
    func setHairColor() {
        print("髪の色:茶色")
    }
    func setSkinColor() {
        print("肌の色:ベージュ")
    }
    func setFaceContour() {
        print("顔の形:丸")
    }
    func setHeight() {
        print("身長:高い")
    }

}

処理を任せられるクラス(移譲先)
avatarDelegateに準拠してメソッドを記述
extensionで設定しているメソッドに関しては記述しなくてもok。記述しない場合はデフォ実装となる

//  Kenta.swift
final class Kenta: avatarDelegate {
	
    func setEyeSize() {
        print("目の大きさ:小さい")
    }
    func setFaceContour() {
        print("顔の形:面長")
    }
    func setHeight() {
        print("身長:低い")
    }

}

処理を任せるクラス(移譲元)

//  Avatar.swift

final class Avatar {

    var delegate: avatarDelegate?  // 処理を任せる相手(インスタンス)を保持する。nilが入る可能性があるのでOptionql型とする。

    func getAvatarInfo() {
        guard let delegate = delegate else {
            // 処理を任せる相手が決まっていない場合
            print("No User")
            return
        }
        if type(of: delegate) == Takuya.self {
            // 処理を任せる相手がTakuyaクラスの場合
            // アバターの設定情報を出力
            // Takuyaインスタンスを参照してメソッドを呼び出している。
            // delegate.setHairLength() デフォルト実装を上書きしてメソッドを呼んでいる
            // delegate.setHairColor() デフォルト実装を上書きしてメソッドを呼んでいる
            // delegate.setSkinColor() デフォルト実装を上書きしてメソッドを呼んでいる

            delegate.setEyeSize()
            delegate.setHairLength()
            delegate.setHairColor()
            delegate.setSkinColor()
            delegate.setFaceContour()
            delegate.setHeight()

        } else if type(of: delegate) == Kenta.self {
            // 処理を任せる相手がKentaクラスの場合
            // アバターの設定情報を出力
            // Kentaインスタンスを参照してメソッドを呼び出している。
            // delegate.setHairLength() 記述しなくてもデフォで呼び出される
            // delegate.setHairColor() 記述しなくてもデフォで呼び出される
            // delegate.setSkinColor() 記述しなくてもデフォで呼び出される
            delegate.setEyeSize()
            delegate.setFaceContour()
            delegate.setHeight()
        }

    }
}

Takuyaに任せる場合

// AvatarViewController.swift
import UIKit
class AvatarViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let avatar = Avatar() // 処理を任せるクラス(移譲元)
        let takuya = Takuya() // 処理を任されるクラス(移譲先)

        avatar.delegate = takuya  // 処理を任せる相手を指定する。avatarインスタンスのdelegateプロパティにtakuyaインスタンスをもたせ保持させる。

        avatar.getAvatarInfo() // avatarインスタンスのメソッドを実行。メソッド内で移譲先のインスタンスのメソッドを呼び出している。
    }
}

実行すると

目の大きさ:大きい
髪の長さ:ロング
髪の色:茶色
肌の色:ベージュ
顔の形:丸
身長:高い

Kentaに任せる場合

//  AvatarViewController.swift
import UIKit
class AvatarViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let avatar = Avatar()  // 処理を任せるクラス(移譲元)
        let kenta = Kenta() // 処理を任されるクラス(移譲先)

        avatar.delegate = kenta // 処理を任せる相手を指定する。avatarインスタンスのdelegateプロパティにtakuyaインスタンスをもたせ保持させる。

        avatar.getAvatarInfo() // avatarインスタンスのメソッドを実行。メソッド内で移譲先のインスタンスのメソッドを呼び出している。
    }
}

実行すると

目の大きさ:小さい
髪の長さ:ロング
髪の色:黒
肌の色:ベージュ
顔の形:面長
身長:低い

となる。

Swiftでよく出てくるデリゲートの説明でUITextFieldやUITavleViewでのデリゲートの説明がありますが
これは移譲元のコードがフレームワーク内に記述されているためブラックボックスでその部分について触れてない解説が多いため意味が分からなくなっているんだなと色々調べていてわかりました。
https://www.egao-inc.co.jp/programming/swift_delegate/

import UIKit
// UITextFieldDelegateに準拠する
// ViewControllerが移譲先にもなっている。
class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var textField: UITextField!
    
    override func viewDidLoad() { // 画面がロードされた際に実行される
        super.viewDidLoad()
        
        textField.delegate = self
        // textFiledのdelegateプロパティにプロトコルに準拠したtextFieldShouldReturnメソッドを含むViewControllerインスタンスをもたせる
        // つまり画面ロード後には画面のテキストフィールドに文字入力してエンターを押すとtextFieldShouldReturnメソッドを含むなんらかの処理が実行されるtextFieldインスタンスができあがる。
        
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        
    }

    @IBAction func tapButton(_ sender: Any) {
        label.text = textField.text
    }
    
    // エンターを押した時に呼ばれるメソッド プロトコルに準拠させるため下記メソッドは記述必須
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        
        label.text = textField.text

        textField.resignFirstResponder() // キーボードを閉じる
        
        return true
    }
    

}

下記がブラックボックスとなっていて分かりにくくなっている。


// 移譲元 
class TextField {
    var delegate: UITextFieldDelegate? // textFieldShouldReturnメソッドを含むViewControllerのインスタンスを保持

    // ここにテキストフィールドがエンターされると delegate.textFieldShouldReturn()を呼び出す処理が記述されているはず
    // delegateはViewControllerのインスタンス。つまりdelegate.textFieldShouldReturn()はViewControllerを参照しているだけ

}

そして上記の理解に辿りついたのは
https://ja.stackoverflow.com/questions/33023/swift-%E3%83%87%E3%83%AA%E3%82%B2%E3%83%BC%E3%83%88%E3%81%AE%E9%80%9A%E7%9F%A5%E3%81%AE%E9%81%8E%E7%A8%8B/33026

わざわざDelegateという名前がついているのだから、さぞ複雑なメカニズムと思われるでしょうが、実態は、ただほかのインスタンスを参照して、そのメソッドを呼んでいるだけです。Delegateの意味は、移譲元のクラスの具体的な実装がわからなくても、プロトコルの宣言を調べることで、どういうメソッドが移譲元から送られるのかがわかることです。
UITextFieldクラスについてみると、その実装に、リターンキーが押された時の処理をするメソッドが定義されており、その定義の中で、DelegateメソッドtextFieldShouldReturn(textField:)を呼んでいることが推理できます。

これが一番分かりやすかったです。参照してるだけなのねと。わざわざ簡単理解させようとしてよけい分かりにくく複雑な説明になっている記事が多くてここに辿り着くのに時間がかかりました。これを読んで、あ〜なるほどと思いました。

現時点で調べたのはここまでで
まだ、具体的な活用方法については深くまで分かってないので要調査。
とりあえず、写経を続けてアプリをいくつか作ってSwiftにもっと慣れるのが先かも。
まだまだ本を2冊読んだだけで全然使えない状態なので。

Swiftにおけるイベント通知のパターンとして
デリゲートパターン
クロージャ
オブザーバパターン
とりあえず、デリゲートパターンについて今回は学習。

この記事が学習メモとして記事なので正しいかどうかは保証できません。
その点を理解した上で参考にして下さい。

あと、間違いがあれば是非指摘お願いします。
Swiftを始めたばかりで理解が間違っている可能性も高いのでよろしくおねがいします。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする