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間で詳細設定画面が通知したい方、大本の設定画面の方が通知されたい方に、このデリゲートパターンが当てはまる。
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 {
これ。なぜかプロトコルなのにクラスの定義みたいな感じにしないといけない、さもないと
'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まだ使えなそう。言語仕様こんだけコロコロ変わるとさすがにキツイよね。