非同期処理の結果をreturnしたかった話
タイトルの通り非同期の処理で取得したデータをreturnしようとして、めっちゃハマりました。
結論
結論としては、非同期処理をreturnすることをあきらめました。非同期処理が終わった後に関数で値を返したい場合にはクロージャを使う方法が一般的のようです。returnで返す方法はググっても見つかりませんでした。
経緯
今年の夏に参加したMasalaというハッカソンで、雨の日にタスクをこなすアプリを作成したのですが、当時Jsonのパースをする方法がわからず文字列でデータを取得し、雨が降っているかを判定していました。
ハッカソンの最中はなんとか形になるものを作るために文字列比較で判定を行なってしまったのですが、後日jsonのパースの方法を調べて、できるようになったのでリファクタリングをしようと試みました。
まず試したコード
関数を作成して、雨が降っているかどうかを返す関数を作成していました。
はじめは非同期になっていることがわかっていなかったので、このようなコードを書いていました。このコードはダメな例です
func judgeWether() -> Bool{
let url = URL(string: openWetherMapAPI)!
let request = URLRequest(url: url)
var task = URLSession.shared.dataTask(with: request){(data, response, error) in
guard let data = data else{return}
do{
let jsonData = try JSONDecoder().decode(Root.self, from: data)
self.wether = jsonData
} catch let e{
print(e)
}
}
task.resume()
if let data = wether{
if(data.weather[0].main == "Rain"){
print(data.weather[0].main)
return true
}
}
// ※wetherがnilなのでここで落ちます!!※
print("jsonData:\(wether.weather[0].main)")
return false
}
コードないのコメントにも書いてありますが、taskを実行する前に後続の処理が呼ばれてしまい、ぬるぽで落ちてしまいました。
まず思いついたこと
まず思いついたこととして、はtaskの中に全ての処理をかいてしまうことです。
var task = URLSession.shared.dataTask(with: request){(data, response, error) in
guard let data = data else{return}
do{
//ここにUIを表示したりアニメーションしたり、画面遷移したり全ての処理をぶち込む
} catch let e{
print(e)
}
}
でもこの方法は、不自然でいやだなーと思っていました。せめて関数化して別クラスに分けておきたいなと。
自分の実現したいこととものすごくかけ離れているように思えていたんですが、クロージャでの実装はこの書き方に近いと思います。
クロージャを使う
クロージャをどう使うか?ということですが、これが非常にわかりにくいんです。
・呼び出し元『viewDidLoad()の中』
メソッドの呼び出し側で何をしているかというと、引数でクロージャを渡しています。
jungeWetherメソッドの中でrain関数が呼ばれた時{ 晴れの処理,雨の処理 }が呼ばれるイメージです。
結局やっていることは、おもいついたことで書いた内容と同じで、非同期処理の中で全ての処理をやっています。
let rainChecker = RainChecker()
rainChecker.judgeWether(rain: {(wether:Bool) in
if(!wether){
self.setRain()
}else{
self.setSunny()
}
})
・関数
関数内の戻り値はなくします、そして引数でクロージャを受け取ります。
そのクロージャを自分の任意のタイミングで呼び出して処理を行なっています。
func judgeWether(rain:@escaping(_ result:Bool)->Void){
let url = URL(string: openWetherMapAPI)!
let request = URLRequest(url: url)
var task = URLSession.shared.dataTask(with: request){(data, response, error) in
guard let data = data else{return}
do{
let jsonData = try JSONDecoder().decode(Root.self, from: data)
if jsonData.weather[0].main == "Rain"{
//引数でもらった関数を実行!
rain(true)
return
}
//引数でもらった関数を実行!
rain(false)
} catch let e{
print(e)
}
}
task.resume()
}