処理の中には、一定の時間がかかるものがあり(インターネット越しにあるAPIサーバーからレスポンスを待つ、など)、その処理が終わった後にプログラムに「これをやっといてね」と「頼み」たいことがよくあります。一般的にはそれらの処理を行う関数をcallback関数と呼びますが、Swiftではclosureを使ったパターン、delegateを使ったパターンがよく見られます。
まず closureで実装した例を説明します。
// PlaySound.swift
class PlaySound {
// [1] 処理に1秒かかる作業を10回実行しているシミュレーション。1秒ごとにcallbackを呼ぶ。callbackはclosure型で指定
public func play(callback: (Int) -> Void) {
for i in 0..<10 {
Thread.sleep(forTimeInterval: 1)
callback(i)
}
}
}
// ViewController.swift
class ViewController: UIViewController {
// PlaySoundクラスのインスタンス
let playSound = PlaySound()
override func viewDidLoad() {
super.viewDidLoad()
// [2] インスタンスメソッドをコールする時に関数を渡している
playSound.play(callback: printNum)
}
// コールバック関数の実装をここで記述出来る
private func printNum(num: Int) {
print(num)
}
}
[1] 関数 play() はclosure型の引数を取ります。
closureとは、名前の無い関数のことで、以下のように( ) -> ( )のような形で書くことができます。最初の( )にはそのclosureの引数を、->(矢印)の後の( )には返り値を記述します。
( ) はtuple(タプル)という型で、複数の異なる型を入れられます。
ここで面白いのは、play( )関数の引数として渡すclosureの型は指定しますが、closureでどのような処理をするのかは、play( )関数を定義する時には決めなくてもよく、その関数を呼ぶ時に決められる、ということです。
[2] 上記の例ではplay( )に渡すためのprintNum(num: Int)という関数をViewControllerクラスで定義し、play(callback: printNum)という形で渡しています。
(Int) -> Void というclosureの型と printNum(num: Int)は合っている必要があります。
func printNum(num: Int) -> Void { // 返り値がない場合 -> Voidは省略可
}
// closureの書き方の例
// 引数なし、返り値なし
() -> ()
() -> Void
// 引数はInt型、返り値はなし
(Int) -> ()
(Int) -> Void
// 引数はIntとFloat、返り値はIntとBool
(Int, Float) -> (Int, Bool)
次に、Delegateパターンの説明をしますが、その前にProtocolについて理解する必要があります。
Protocolとは、この処理を担当するなら、これらの関数(や変数)は備えといてね、という「規約」を決めるものです。
// PlaySound.swift
// [1] Protocolを定義
protocol PlaySoundDelegate {
func printNum(num: Int)
}
class PlaySound {
// [1] Protocol型の変数を宣言
var playDelegate: PlaySoundDelegate!
public func play() {
for i in 0..<10 {
Thread.sleep(forTimeInterval: 1)
// ここでProtocolの関数を使う
playSoundDelegate.printNum(num: i)
}
}
}
// ViewController.swift
class ViewController: UIViewController,
PlaySoundDelegate // [2]-b Protocolに準拠
{
let playSound = PlaySound()
override func viewDidLoad() {
super.viewDidLoad()
// [2]-a delegateをself(ViewControllerクラス)に指定
playSound.playSoundDelegate = self
playSound.play()
}
// [2]-c Protocolの関数を定義
func printNum(num: Int) {
print(num)
}
}
[1] delegateは「任せる」という意味です。PlaySoundクラスの色々な処理が完了したら、その次に任せたい処理(関数)をProtocolに記述します。Protocolには関数の宣言だけをし、定義はProtocolに準拠するクラスに記述します。
[2] 上記の例ではViewControllerクラスをPlaySoundDelegate Protocolに準拠させることにします。
[2]-a playSound.playSoundDelegate = self はViewControllerクラスがdelegateを担当しますよ、と指定しています。
[2]-b その際、クラスがProtocolに準拠していないとエラーになります。
[2]-c また、Protocolの必須関数を定義していないとエラーになります。
どちらの実装方法でも同じようなことは実現出来るので、まずは一つのやり方にじっくり慣れると、より理解が深まると思います。