euphonictechnologies’s diary

Haskell超初心者の日記です。OCamlが好きです。

follow us in feedly

Swift - delegateを実装する、通知する、通知される

protocolの仕様は未だに不安定

Appleは未だにこの辺りの仕様をXcodeのバージョンを上げるたびにコロコロ変えているように見えます。この情報は2014/10/25日現在私がXcode ver 6.1(611052d)で確認した内容です。

delegateってどんなだっけ

おさらい。デザインパターンというやつですね。登場人物は

  • 通知がほしいReceiver
  • 通知ををくりたいSender

と単純です。Senderはプロパティとしてdelegateをもっていて、ReceiverはSenderのdelegate変数に自分自身をセットします。 SenderはReceiverに通知するとき、delegateプロパティのReceiverに予め取り決めておいたメッセージを送って(Objective-C風の言い方。つまり予め決めておいたメソッドをコールする)、通知をします。

当然この"予め取り決めておいた”があるのでprotocolを使ってこの部分を実装します。つまり、実装するときに必要なファイルは2つで

  • Sender.swift
    • protocol SenderDelegateの定義。このプロトコルの関数で通知を行う
    • Senderクラスの定義。プロパティとしてdelegate: SenderDelegateを持つ
  • Receiver.swift
    • Receiverクラスの定義。SenderDelegateプロトコルを実装する

という感じになるはず。

具体的な例

私が必要だったのは古典的なUITableViewが2つある設定画面で、大本の設定画面の中で、右に矢印付いている項目を押すとその項目の詳細設定画面にスライドして、リストの中からその項目に設定したいものを選ぶとそれを大本の設定画面に戻してあげる、というもの。 2つのViewController間で詳細設定画面が通知したい方、大本の設定画面の方が通知されたい方に、このデリゲートパターンが当てはまる。

f:id:euphonictechnologies:20141025183828p:plain

Swiftで書いてみる

私が最初試して困ったコードを掲載してもしょうがないので、最終的に辿り着いた答えのコードだけ掲載して私がハマった落とし穴を後掲します。

Sender側実装

protocol MyViewDelegate: class {
    func finishSelection(selectedItemId: Int)
}

class MyViewControllerSender: UIViewController, UITableViewDataSource, UITableViewDelegate {
...
    var selectedItemId: Int
    weak var delegate: MyViewDelegate? = nil
...
    @IBAction func back(sender: AnyObject) {
        NSLog("Sending the data to receiver, and exiting from the view")

        delegate?.finishSelection(selectedItemId)

        self.dismissViewControllerAnimated(true, completion: nil)
    }
...
}

Receiver側実装

class MyViewControllerReceiver: UIViewController, UITableViewDataSource, UITableViewDelegate, MyViewDelegate {

...

    // Called right before the view transition by segue happens
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if(segue.identifier == "segue") {
            let vc: MyViewControllerSender = segue.destinationViewController as MyViewControllerSender
            vc.delegate = self
        }
    }

    // Called and receive data from detail table view
    func finishSelection(selectedItemId: Int) {
        //
        NSLog("Selected is \(selectedItemId)")
    }
...
}

こんな感じ。一見普通ですが、

protocol MyViewDelegate: class {

これ。なぜかプロトコルなのにクラスの定義みたいな感じにしないといけない、さもないと

f:id:euphonictechnologies:20141025185652p:plain

'weak' cannot be applied to non-class type 'MyViewDelegate'

と怒られる。

How can I make a weak protocol reference in 'pure' Swift (w/o @objc) - Stack Overflow

なんと。

ここはweakにしておかないとこのビューのライフサイクルに元画面のライフサイクルが引っ張られるのでweakにすべきなので、classをプロトコルにつけておかないといけない。

まとめ

企業とかではSwiftまだ使えなそう。言語仕様こんだけコロコロ変わるとさすがにキツイよね。