はじめに
お久しぶりです
年末に差し掛かり忙殺されてきました
ところでコールバック地獄をご存知ですか?
私もふとした時にはこの地獄に足を踏み入れそうになりますが思い留まろうともがいています
そんな地獄を見ないために悪い例と良い?例を紹介します
準備
podを使ってAlamofire,SwiftyJSON,PromiseKitを入れました
podの使い方については良い記事がたくさんありますのでそちらを参照ください
target 'promiseme' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
# Pods for promiseme
pod 'SwiftyJSON'
pod 'PromiseKit'
pod 'Alamofire'
end
3分クッキング方式です
メインのViewにUITextViewとUIButtonを配置して参照させておきます
簡単にですがqiitaの記事一覧取得とURLが有効かを確認する通信処理を関数化して用意しておきます
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
@IBOutlet weak var txtArea: UITextView!
@IBAction func btnDown(_ sender: Any) {
}
func getQiitaList( afterFunc: @escaping (_ url:String)->Void ){
let url = URL(string: "http://qiita.com/api/v2/items")!
let parameters: Parameters = ["page": 1, "per_page": 10]
Alamofire.request(url, method: .get, parameters: parameters )
.responseJSON { response in
switch response.result {
// 処理成功時
case .success(let value):
let json = JSON(value)
let arrJson = json.array
for m in arrJson! {
let url = m["url"].rawString() ?? ""
afterFunc(url)
return
}
break
// 処理失敗時
case .failure(let error):
print(error)
break
}
}
}
func getQiitaTerm( strUrl:String, afterFunc: @escaping (_ text:String)->Void ){
let url = URL(string: strUrl)!
Alamofire.request(url, method: .get )
.response { response in
guard let status = response.response?.statusCode else { return }
let text = strUrl + " " + String(status)
afterFunc( text )
}
}
やりたいことは
記事取得ボタンを押すとQiitaの記事一覧を取得する
するとレスポンスにURLが含まれているので記事の1件についてそのURLをGETしてHTTPステータスをURLとともに表示します
内容は変でしょうもないですがご勘弁ください
まず準備として通信処理関数には通信処理が完了したら実行しておきたいコールバック処理を渡すようにしてあります
なので呼び元ではコールバック処理を定義しておいて渡すことになります
コード悪い例
すごく単純に書いた場合はこのように書けるんじゃないでしょうか
@IBAction func btnDown(_ sender: Any) {
self.txtArea.text = ""
let afterFunc = { (_ url:String) in
let afterFunc2 = { (_ text:String) in
self.txtArea.text += text + "\n"
}
self.getQiitaTerm(strUrl: url, afterFunc: afterFunc2)
}
self.getQiitaList(afterFunc: afterFunc)
}
特に問題なく動きます
でもどうでしょうか?
コールバック処理の中にコールバック処理が書いてあって、すごく少ない処理なのにもう嫌になってきませんか?
これで処理の中身が複雑になったり長くなったり、コールバックの回数が増えていくとネストがすごすぎて可読性が悪化していきます
これを無名関数で書いていくともっと分かりにくいかもしれません
コード改善例
PromiseKitを使って改善します
@IBAction func btnDown(_ sender: Any) {
self.txtArea.text = ""
// let afterFunc = { (_ url:String) in
//
// let afterFunc2 = { (_ text:String) in
// self.txtArea.text += text + "\n"
// }
// self.getQiitaTerm(strUrl: url, afterFunc: afterFunc2)
//
// }
// self.getQiitaList(afterFunc: afterFunc)
_ = Promise<String> { seal in
self.getQiitaList(afterFunc: { (_ url:String) in
seal.fulfill(url)
})
}.then { url in Promise<String>
{ seal in
self.getQiitaTerm(strUrl: url, afterFunc: { (_ text:String) in
seal.fulfill(text)
})
}
}.then { text in Promise<Void>
{ seal in
self.txtArea.text += text + "\n"
seal.fulfill_()
}
}.then { Void in Promise<Void>
{ seal in
// 他のコールバック処理好きなだけ
seal.fulfill_()
}
}.then { Void in Promise<Void>
{ seal in
// 他のコールバック処理好きなだけ
seal.fulfill_()
}
}.ensure {
// nothing to do
}.catch { error in
print(error)
}
}
余計に長くなってしまった・・・
あんまり参考にならんかも
けどこれでコールバックによってはネストがこれ以上は深くならずに済みます
この難しさを1度許容してしまえば後はこれ以上は難しくならずに済みます
処理の順番も意識しやすくなるんじゃないでしょうか
Promiseに限らず非同期処理を扱う仕組みや書き方というのはありますので**「適切」**に用いるのが良いでしょう
