##はじめに
今回僕は、位置情報から国土地理院のAPIを使って標高を取得し、表示するという簡単なアプリをMVCで書こうと思った際にあるところでつまずきました。MVCで書こうと思ったのはなんとなくかっこいいし、きれいだから。
具体的に言うと、ModelファイルでAPIを利用して取得した標高をControllerファイルに渡す際につまずきました。非同期通信らへんの問題です。どうしてもうまく行かなくて、teratailで質問してみたところ親切な人に丁寧な回答をもらい解決することができました。ありがとうございました。
回答にはクロージャを使っていました。初心者の敵クロージャです。しかし今回始めてそのクロージャの使い道と便利さがわかったような気がしたので、よかったら最後まで見てください。
##大まかな流れ
Modelで標高を取得し、それをControllerを通じてViewにわたすという操作をしています
//位置情報が更新されたときに呼ばれるメソッド
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
DispatchQueue.main.async {//UIの更新はメインスレッドで行う
//位置情報が入ったlocationsをModelに渡して標高を取得し
//その情報をViewに渡してUIを更新する
customView.elevation = model.getElevation(locations)
}
}
var elevation = 0.0
func getElevation(_ Locations: [CLLocation]) -> Double {
//通信処理の準備
APIを使った通信処理 { (data, response, error ) in
//デコードしたりなんやかんやで標高を取得する
elevation = 取得した標高
}
//取得した標高を返す
return elevation
}
##つまずきポイント
どうやってもgetElevationで取得した標高をControllerを通じてViewにわたすことができない。
非同期通信についてたくさん調べてやってみたが全然うまく行かない。通信処理からelevationに標高を代入する前にgetElevationメソッドが終了してしまい、取得した標高をUIに表示することができませんでした。そんなときに助けてくれたのがクロージャでした。
##ここで役に立つのかクロージャ!
以前クロージャの記事を書いたが、いまいち使い所がわからなかった。
Swiftクロージャ これいつどうやって使うの?
ところが今回なんとなくわかった、気がする。
まず今回うまく行かなかった原因は、取得した標高を変数に代入する前にメソッドが終了して、意図した戻り値を返すことができていなかったからです。
これを解決するためにreturnを使うのをやめ、通信処理が完了したらクロージャを実行して値を返すようにします。
##クロージャの出番
今までのコードを以下のように書き換えます
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
model.getElevation(locations, completionHandler: { (elevationString) in
DispatchQueue.main.async {
self.customView.elevation = elevationString ?? "標高を取得できませんでした"
}
})
}
var elevationString = "0.0m"
func getElevation(_ Locations: [CLLocation], completionHandler: @escaping (String?) -> Void) {
//通信処理の準備
APIを使った通信処理 { (data, response, error) in
//デコードしたりなんやかんやで標高を取得
elevationString = 取得した標高
completionHandler(elevationString)
##解説
最初にこの書き方を見たときは少し怯んだのですが、自分なりにコードの解説をしてみます。
###①クロージャの設置
まずクロージャを使ってcompletionHandlerというものを定義しています。このcompletionHandlerはString?型の変数を持っています。
###②クロージャに値を代入
国土地理院から標高を取得するという通信処理が完了したら、クロージャを呼び出してもらってその標高を代入します。
###③代入された値を利用する
代入された値を利用して、Viewに渡します。
実は③の部分は最初から使っていた以下のコードと同じ操作をしています。
APIを使った通信処理 { (data, response, error) in
//dataを解析したり
//errorが返ってきたら出力したり
}
今回だと通信処理が完了するまではクロージャは呼び出されず、何も値を持っていません。通信処理が完了したらクロージャが呼び出され値を代入し、その値をViewに渡すというふうになっています。
##まとめ
まだまだ理解しきれていないところはたくさんあります。
-
@escaping
について - 循環参照について
クロージャの使い所を実感できただけでも成長したなと思いました。修正やもっとこんなふうに書いたほうがいいよなどありましたらお願いします。