Closureの使い方です。Swiftをいじっていると、よく出てきますが、これは実は無名関数を引数に含んでいる関数を、見やすく書いているだけなのです。
また、無名関数自体は引数と戻り値の型を指定するだけで良いので、その関数の呼び出し先では、それらの戻り値の加工は指定された型に従うのであれば、自由に行うことができます。
<h3> 実行環境 </h3>
Swift:5.0
Xcode:10.2.1
<h3> Closureの使い方 </h3>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import UIKit class ViewController: UIViewController { @IBOutlet weak var button: UIButton! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } //callback:無名関数として記述 func sample(title:String, callback:(String)->(String)){ print("処理1") print("\(callback(title)):処理2") } @IBAction func tapButton(_ sender: Any) { //呼び出し側では、{}の中に無名関数を書いている。testという変数は宣言不要。 sample(title: "テスト") { test in //Stringを返すことに従えば、あとはどのように加工してもOK return "\(test)です" } } } // 処理1 テストです:処理2 |
活用例2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import Foundation class model{ var userName:String = "" var userWeight:Double = 0.0 func update(_ name:String, weight:Double, callback:(String)-> (String)){ print("処理1") self.userName = name self.userWeight = weight print(callback("私は\(name)で、体重は\(weight)です:処理2")) } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import UIKit class ViewController: UIViewController { @IBOutlet weak var nameField: UITextField! @IBOutlet weak var weightField: UITextField! @IBOutlet weak var registerButton: UIButton! var userModel = model() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } @IBAction func tapButton(_ sender: Any) { let userName = nameField.text let userWeight = Double(weightField.text!) userModel.update(userName!, weight: userWeight!){ parameter in return "\(parameter)が入力されている" } } } //てすと、50.5と入力し、ボタンを押したとき 処理1 私はてすとで、体重は50.5です:処理2が入力されている |
不可解なのが、活用例1と活用例2で、コールバック関数が呼び出されるタイミングが異なること。
活用例1)
呼び出される側の関数処理(処理1)→呼び出し側のコールバック関数の処理→呼び出し側で、コールバック関数を引数にとる処理(処理2)
活用例2)呼び出される側の関数処理(処理1)→呼び出し側で、コールバック関数を引数にとる処理(処理2)→呼び出し側のコールバック関数の処理
~~活用例1だけを見ていると、引数にとられている無名関数、つまりコールバック関数が戻り値を返す処理を行ったあとに、それを使う処理が呼ばれていますが、それは担保されるわけではないようです。~~
→print文の書き方が、意図したものと違っていました。下記のように書けば活用例1と同じ順番のプリント文がコンソールに表示されました。
1 2 3 4 5 |
print("\(callback("私は\(name)で、体重は\(weight)です")):処理2") //てすと、50.5と入力し、ボタンを押したとき 処理1 私はてすとで、体重は50.5ですが入力されている:処理2 |
結局のところ、処理の順番としては、
callback関数の引数の値が決まる→クロージャーの中で使うパラメーターにその値がうつる→戻り値がreturnされる
というだけのことなので、よく分からなくなったら、ここに立ち返れば良い。つまり、
{ parameter in
return …
}
の中身は、parameterの中身が決まってから処理される→{}の処理が終わる→returnされる値を呼び出される側の関数内で使えるようになる、という順番。
ここから考えられるのは、コールバック関数の引数の値が決まらなければ、コールバック関数は呼びようがないということ。つまり、ある特定の値を取得してから、処理を走らせたいような場合に使いやすい処理だということです。
例えば、CloudFireStoreでドキュメントを取得してくるときの書き方は、ドキュメントを見ると下記のようになっている。これは、querySnapshot,errというコールバック関数の引数を取得してから、それらをビューに反映させる処理を走らせることを意図していると思われます。
1 2 3 4 5 6 7 8 9 |
db.collection("cities").getDocuments() { (querySnapshot, err) in if let err = err { print("Error getting documents: \(err)") } else { for document in querySnapshot!.documents { print("\(document.documentID) => \(document.data())") } } } |
ちなみに、Xcode上で上記のような、コールバック関数を引数に持つ関数を呼ぶときは、コールバック関数の箇所をダブルクリックすれば自動的にクロージャーで表示がされます。このとき、下記のようにコールバック関数の引数部分も一緒に表示されるのですが、適当な単語で埋めておけば大丈夫です。仕組みを知らないと、いきなりエラーが出てXcodeに怒られて訳わからん、となりがちですが、単なる引数なので好きな名前で置き換えてしまいましょう。
1 2 3 4 |
let url = URL(string: "https://www.google.com/?hl=ja")! session.downloadTask(with: url) { (<#URL?#>, <#URLResponse?#>, <#Error?#>) in <#code#> } |
例えば下記のように。
1 2 3 4 |
let url = URL(string: "https://www.google.com/?hl=ja")! session.downloadTask(with: url) { (data, response, error) in print("ダウンロード完了後に呼ばれる") } |
なお、コールバック関数が戻り値を返さない形でも定義することができ、その場合はreturnされる値をどうこうしたいという目的よりも、parameterの値に対して、その関数の定義側で特定の値を入れてあげるという狙いでも使える。
例えば、下記のようにViewControllerでユーザーからのアクションを検知→モデルクラスでデータを更新→更新された値をコールバック関数の引数として宣言→ViewControllerのビューにその値を表示する、といったような活用の仕方も可能。
こうすることで、データの値の更新処理をモデル側のクラスに記述でき、ビュー側の記述と責務を減らすことができる。また、ビュー側がモデルを一方的に参照しているだけなので、うっかり weak selfをつけ忘れて循環参照になってしまうリスクも無い。なんだかこちらの方が利用用途として多いような気がしてきた。
1 2 3 4 5 6 7 8 9 10 11 |
import Foundation class model { var number:Int = 0 func updateModel(_ callback:(Int) -> ()){ number = number+1 callback(number) } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import UIKit class ViewController: UIViewController { @IBOutlet weak var label: UILabel! @IBOutlet weak var button: UIButton! var sampleModel = model() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } @IBAction func tapButton(_ sender: Any) { self.sampleModel.updateModel(){ data in self.label.text = String(data) } } } |
また、コールバック関数一般のメリットとして、呼び出し先の記述で処理を加工できる、というところがあるようだ。
参考)
http://e-words.jp/w/%E3%82%B3%E3%83%BC%E3%83%AB%E3%83%90%E3%83%83%E3%82%AF%E9%96%A2%E6%95%B0.html
まとめると、コールバック関数のメリットは、
・呼び出される関数の処理の中で、呼び出し側で処理したコールバック関数の戻り値を使える
・呼び出される関数の中で処理した値を、呼び出す側で処理するコールバック関数の引数に入れることができる
・コールバック関数は、呼び出し先の記述で処理を加工できる
利用シーンは、
・特定の値を取得してから、処理を実行したいとき
・ビューとモデルの責務の切り分けたいとき
といったところか。