APIからjsonを取得しようと思った際に、なんども使うことになりそうなので、APIを叩く関数を作り、それを呼び出すようにしようと思いました。
調べたらswiftでjsonを扱うライブラリはAlamofireが有名らしいのでそれを使うことにしました。
環境
- MacOS Mojave 10.14.2
- Xcode10.1
- Swift 4.2.1
最初にやろうとしたこと
まず最初に、単純に関数内でapiを叩いてその値を返すようにしようとおもい下記のように書いてみました。
import Alamofire
import SwiftyJSON
func getJsonData(url: String) -> JSON {
var json = JSON()
Alamofire.request(url).responseJSON { response in
print("in response")
switch (response).result {
case .success(let Value):
print("success")
json = JSON(Value)
case .failure(let Error):
print(Error)
json = JSON(Error)
}
}
return json
}
URL文字列を取得して、JSONを返すことをしようとしています。これを使いたいところで呼び出してみたところ、
let jsonData = HandleDataUtil().getJsonData(url: requestURL) // HandleDataUtilは上記関数を書いてるクラス
print(jsonData)
コンソールには下記のように表示されました。(全部の修正完了後に思い出し思い出し書いてるのでちょっと違うかも...)
in response
success
{
}
通信は成功してるけど、データが返ってきていません。レスポンスが返って来る前に関数の処理を抜けてしまっているようです。
どうすればいいのか調べていたら、参考1を見つけました。
つまりは、
- { response in ...} の部分は完了ハンドラとして渡される
- その完了ハンドラが渡されるのは通信完了後
- しかし、return jsonが実行されるのは通信完了前
ということみたいです。つまり上記でも言った「レスポンス返って来る前にメソッドを抜けてる」ってことです。
どうするか
対処法は
- 自分で定義したメソッドも完了ハンドラパターンにする
つまりは
- 返り値と受け取りたい型を引数とするクロージャ型をメソッドの引数に追加
- Alamofireの完了ハンドラのなかで自前の完了ハンドラを呼ぶ
ことで解決できます。
最終的に下記のようなコードで実現できました。
func getJsonData(url: String, completion: @escaping (JSON) -> Void) {
Alamofire.request(url).responseJSON { response in
print("in response")
switch (response).result {
case .success(let Value):
print("success")
completion(JSON(Value))
case .failure(let Error):
print(Error)
completion(JSON(Error))
}
}
}
呼び出し側は
var jsonData = JSON()
HandleDataUtil().getJsonData(url: requestURL) { json in
jsonData = json
print(jsonData)
}
となります。なんか、最終的にやりたいことと違くなってしまった・・・。
結論
ここまできてなんですが、最終的に思ったのが「メソッドにする価値がないな」です。理由は
- 成功した場合、失敗した場合のハンドリングができない
- 記述量に大差がない。結局クロージャを使ってるので、これならAlamofireベタ書きと大差ない
- むしろAlamofireそのまま書いた方が処理の流れが見やすい。
そもそもAlamofire自体、非同期通信をSwiftで簡単にかけるようにしてくれるライブラリですから、そのまま使えばよかったんだなと反省してます。