Xcode
iOS
Swift

delegateの自分なりの解釈 (Swift)


概要

今回はSwiftでdelegateを使ってみようということで記事を書かせていただきます.


環境

Swift 4.2


Amazonと551と私

デリゲートとはなんぞやの前に、まずあるストーリーを想像してみてください.

あなたは今 とある人間Aくんです.

Aは今 とてもつもなく551が食べたいです.

d001-main.jpg

美味しそうですね...

しかし!!!

Aは今東京に住んでいます...

本場の肉まん551を食べるには大阪まで行かないといけません. (ほなイコカ)

そこで

Amazonに配達をお願いします

IMG_0952.PNG

Amazonに頼めば仮に東京にいても美味しい551が食べられるのです.

つまり、自分がやりたくない処理を他の誰かにお願いし任せること(移譲)がdelegateの役割です.

では、delegateの解説を始めていきたいと思います.


delegateについて

ではdelegateとはなんぞやということで.


あるクラスから処理の一部を他のクラスに任せたり、他のクラスへメッセージを送る等の目的でよく使われるデザインパターン

引用:http://nukenuke.hatenablog.com/entry/2015/09/17/120749


とのことです.つまり処理をお願いすることです. 難しい言葉で言うと移譲です.残念ながら僕は最初その言葉を聞いてあまりピンと来ませんでした.

なんにせよ具体的な使い方を覚えたほうが早いと思うので先ほどの話を用いてコードを書いていきましょう

ButamanPersonInTokyoをA君として、Amazonクラスを用いて考えていきましょう.


ButamanPersonInTokyoAmazongogoichiという変数をdelegateを用いて送ってみる(PlayGround)


STEP0.GogoichiクラスとGogoichiKindを実装

本題とは関係ないのでPlayGroundにコピペでOKです.

enum GogoichiKind: String {

case Butaman = "豚まん"
case Dango = "団子"
case Gyoza = "餃子"
}

class Gogoichi {

var price: Int {

switch kind {
case .Butaman:
return 170
case .Dango:
return 30
case .Gyoza:
return 30
}
}

var kind: GogoichiKind = .Butaman
}


STEP1. protocolの宣言

protocol SendGogoichi {

func send(gogoichi: Any) -> Void
}

ポイント. 呼ばれるメソッドはprotocolの中で定義のみしないといけない. (処理は書かないと言う意味です)


STEP2. ButamanPersonInTokyoの宣言. delegateの宣言

class ButamanPersonInTokyo {

var delegate: SendGogoichi?
}

ポイント. var delegate: SendGogoichi?を処理をお願いする側で宣言しておく


STEP3. Amazonの宣言. protocolを継承. 同時にプロトコルのメソッドを宣言.

class Amazon: SendGogoichi {

func send(gogoichi: Gogoichi) {
}
}

ポイント. SendGogoichiプロトコルを処理を任された側で継承しておく


delegateを設定する.

delegateを設定するとはどのクラスが処理をお願いしてどのクラスが処理を任されるのかを決めることです。

お願いされる側はprotocol継承している方でした.Amazonの方です.

お願いをする側はdelegate宣言している方でした.ButamanPersonInTokyoの方です.

delegateの宣言の仕方は

var delegate: SendGogoichi?

でした. このSendGogoichiはプロトコルである必要があります.

宣言したdelegateにお願い先を代入

delegate = Amazon()

これを書いてあげればいいのですが, delegateは代入してあげないと初期値がnilなのでOptional Chainingした時にメソッドが呼ばれません.

delegate: SendGogoichi? //nilが入っている

delegate?.send(gogoichi: gogoichi) //sendメソッドはdelegateがnilなので呼ばれない.

なので

delegate = Amazon()

delegate?.send(gogoichi: gogoichi)

のようにdelegate?.send(gogoichi: gogoichi)よりも前にdelegateに値をいれてあげる必要があります.


ButamanPersonInTokyoAmazongogoichiをお願いするメソッドを書く.

func orderButaman() {

let gogoichi: Gogoichi = Gogoichi()
gogoichi.kind = .Butaman
delegate?.send(gogoichi: gogoichi) // ここで処理をお願いしている
}

このorderButamanメソッドでdelegate?.sendが呼ばれるという形になっています.


任される側のクラス(Amazon)で送信する処理を実装する.

func send(gogoichi: Gogoichi) {

for _ in 0...2 {
sleep(1)
print("配達中...")
}
print("\(gogoichi.kind)の送信を完了しました.値段は\(gogoichi.price)円です.")
}

sleep(1)は配達には時間がかかることを想定して処理に時間をかけてます.


インスタンス化

let butamanPerson = ButamanPersonInTokyo()

butamanPerson.delegate = Amazon()

butamanPerson.orderButaman()


全体のコード

import UIKit

import Foundation

enum GogoichiKind: String {

case Butaman = "豚まん"
case Dango = "団子"
case Gyoza = "餃子"
}

class Gogoichi {

var price: Int {

switch kind {
case .Butaman:
return 170
case .Dango:
return 30
case .Gyoza:
return 30
}
}

var kind: GogoichiKind = .Butaman
}

protocol SendGogoichi {

func send(gogoichi: Gogoichi) -> Void
}

class Amazon: SendGogoichi {

func send(gogoichi: Gogoichi) {

//
for _ in 0...2 {
sleep(1)
print("配達中...")
}
print("\(gogoichi.kind)の送信を完了しました.値段は\(gogoichi.price)円です.")

}
}

class ButamanPersonInTokyo {

var delegate: SendGogoichi?

func orderButaman() {

let gogoichi: Gogoichi = Gogoichi()
gogoichi.kind = .Butaman
delegate?.send(gogoichi: gogoichi)
}
}

let butamanPerson = ButamanPersonInTokyo()

butamanPerson.delegate = Amazon()

butamanPerson.orderButaman()


コンソール

スクリーンショット 2018-11-25 2.02.21.png


なぜデリゲートを使うのか

薄々感づいた人もいると思いますが, これdelegateなくても書けます.

IMGL8663171031_TP_V.jpg

delegateを使うメリットの1つとして処理を任せた相手がどんなクラスであるのかを考えなくていいという点があります.

今回はdelegateのメリットはあまりありませんでしたが、もしAmazonではなく大阪の友達gogoichiを送ってくれたらどうでしょう

この実装の変更の流れをみていきます。


 もしdelegateを用いていなかったら

class ButamanPersonInTokyo {

func orderButaman() {

let gogoichi: Gogoichi = Gogoichi()
gogoichi.kind = .Butaman
Amazon().send(gogoichi: gogoichi) // ここ
}
}

こうやって直接読んであげることになります.

これを大阪の友達に頼むと

class ButamanPersonInTokyo {

func orderButaman() {

let gogoichi: Gogoichi = Gogoichi()
gogoichi.kind = .Butaman
大阪の友達().send(gogoichi: gogoichi) // ここ
}
}

こうなりますよね

しかし


delegateを用いると

ButamanPersonInTokyoはコードを変更する必要がありません.

class ButamanPersonInTokyo {

var delegate: SendGogoichi?

func orderButaman() {

let gogoichi: Gogoichi = Gogoichi()
gogoichi.kind = .Butaman
delegate?.send(gogoichi: gogoichi)
}
}

このままで大丈夫です.

インスタンス化した時にdelegateに入れる相手を変更すればいいんです

class OsakaFriend: SendGogoichi {

func send(gogoichi: Gogoichi) {

for _ in 0...2 {
sleep(1)
print("配達中...")
}
print("\(gogoichi.kind)の送信を完了しました.値段は友達なので無料だよ.")
}
}

let butamanPerson = ButamanPersonInTokyo()

butamanPerson.delegate = OsakaFriend()


コンソール

スクリーンショット 2018-11-25 2.13.03.png


まとめ

適当な話の下りでしたがdelegateは理解できたでしょうか/

delegateを使うことで得られる

classのように再利用できる

お願いする先を気にしなくて良い

の2つの利点について考えてみました。

classのように再利用できる点についてはSwiftオブジェクト指向ならぬプロトコル指向として捉える考えもあるようでプロトコルは重要なのではと感じています.

自分なり砕けて説明してみたのでもしここは違うよというところがあればアドバイスください。

最後まで読んでいただきありがとうございました!