導入
サンプルコードには ViewController と ModalView が登場します。
ここでは ViewController が ModalView を持っているため、
ViewController の側から、 ModalView の持つ値を変更することができます。
ですが逆に ModalView が ViewController の持つ値を変更することはできません。
それを出来るようにするために、
プロトコルとデリゲートの設定を書いていきます。
手順を簡単に説明
まずサンプルコードと簡単な手順の説明です。
ModalView
ViewControllerからモーダルで遷移するView。
TextFieldを持っていて、モーダルを閉じるときそこに書かれている値をViewControllerのプロパティに反映させたい。
@objc protocol ModalViewDelegate {
// 1. protocolの定義
func modalView(text: String)
// 6. 使いたいViewControllerのメソッド(5と同じもの)を定義
}
class ModalView: UIView {
weak var customDelegate: ModalViewDelegate?
// 2. ModalViewのプロパティを設定
var textField = UITextField()
func close() {
customDelegate?.modalView(textField.text)
// 7. ViewControllerのメソッドを実際に使う
}
}
ViewController
labelとmodalViewを持っている。
modalViewからlabelの値を操作してもらいたい。
class ViewController: UIViewController, ModalViewDelegate {
// 3. ViewControllerにModalViewDelegateを実装
let label = UILabel()
func showModal() {
let modalView = ModalView()
modalView.customDelegate = self
// 4. modalViewのcustomDelegateプロパティにself(ViewController)を定義
}
func modalView(text: String) {
label.text = text
}
// 5. ModalViewに使ってほしいメソッドを定義
}
手順を順番に説明
以下、一つ一つ手順を説明をしていきます。
-
protocolの定義
ModalViewにprotocol自体を定義します。
@objc protocol ModalViewDelegate { // 1. protocolの定義 }
まだ中身はなくても大丈夫です。
-
ModalViewのプロパティを設定
ViewController の値を変更するためには、
ModalView が ViewController を持っていなくてはいけません。そのため ModalView に対し、
ViewController を入れるべき、 ModalViewDelegate? 型のプロパティを定義します。class ModalView: UIView { weak var customDelegate: ModalViewDelegate? // 2. ModalViewのプロパティを設定 }
-
ViewControllerにModalViewDelegateを継承する
先ほど ModalView に ModalViewDelegate? 型のプロパティを設定しましたが、
ViewController は ModalViewDelegate 型ではないので、このままではプロパティに持てません。なので ViewController に ModalViewDelegate プロトコルを実装します。
class ViewController: UIViewController, ModalViewDelegate { // 3. ViewControllerにModalViewDelegateを実装 }
-
modalViewのcustomDelegateプロパティにself(ViewController)を定義
そして ViewController から modalView のcustomDelegateプロパティに self を設定します。
ここでいう self は ViewController のことですね。
func showModal() { let modalView = ModalView() modalView.customDelegate = self // 4. modalViewのcustomDelegateプロパティにself(ViewController)を定義 }
これで ModalView がプロパティに ViewController を持ち、
ViewController のメソッドを使うことができるようになりました。
-
ModalViewに使ってほしいメソッドを定義
さらに ModalView に使ってほしいメソッドを ViewController に定義します。
func modalView(text: String) { label.text = text } // 5. ModalViewに使ってほしいメソッドを定義
-
使いたいViewControllerのメソッド(5と同じもの)を定義
さきほど ViewController に定義したメソッドを ModalViewDelegate の中にも書く必要があるので以下のように書いておきましょう。
@objc protocol ModalViewDelegate { // 1. protocolの定義 func modalView(text: String) // 6. 使いたいViewControllerのメソッド(5と同じもの)を定義 }
-
ViewControllerのメソッドを実際に使う
これでようやく ModalView から ViewController のメソッドを使うことができるようになりました。
なのでViewControllerの持つ値を変更したいところで、
ModalView のプロパティであるcustomDelegate?(中身は ViewController)の、modalView(text) メソッドを使用します。そうすることで ViewController のメソッドを、引数を持って呼び出すことができ、
そのメソッド内で ViewController の持つ値を変更することができました。func close() { customDelegate?.modalView(textField.text) // 7. ViewControllerのメソッドを実際に使う }
weak varについて
ここまででプロトコルとデリゲートについて、やっていることや設定の手順は理解できたと思います。
ですがここで ViewController と ModalView は相互に持ち合う(参照し合う)という、通常よくないことが起きてしまっています。
まず、相互に参照し合うことがなぜ良くないのかという話からしましょう。
iPhoneアプリにはメモリという作業領域があります。
メモリは容量がいっぱいになるとアプリが落ちてしまうので、使わないものは作業領域から消すことが重要になります。
そこでswiftではインスタンスがあると、その分メモリを専有してしまうため、使わないときは自動でメモリ領域から消してくれます。
このとき、使っている使っていないをどう判断しているかというと、
参照されているかされていないかで判断しているのです。
なので、相互に参照し合っているというのは、常にメモリ領域をしてしまうため、良くないということなんですね。
その相互参照を解決しているのが ModalViewのプロパティ宣言にある weak です。
weak var customDelegate: ModalViewDelegate?
weak は 弱参照 とも呼ばれ、
通常の定義(varやlet)とは対称的に強参照とも呼ばれます。
さきほど 参照されているかされていないか で判断すると言ったのですが、
具体的には、強参照があると 参照カウント(参照されている数)が増え、この参照カウントが1以上のときは、参照されているとみなされます。
そして、弱参照はこの 参照カウント を増やしません。
つまり、強参照がなく、弱参照のみになると、インスタンスは破棄され、メモリを空けてくれる、ということなんですね。
デリゲートは性質上、相互参照が常に起こってしまうので、
このように片方は 弱参照 を用いることが慣習となっています。