問題点
SwiftでのiOSアプリ開発において、
Alamofire*1とか通信系の関数を使用すると、
コールバックの中身の実行が通信終了を
待たずして別の処理が走ってしまう。
意図した処理が実装できず、例えば
下図のようにAPIを叩いた結果が
テーブルに表示されない悲しい事例も見られる*2。
このような事態に対応するべく、
javascript/jQuery界隈では
Deferredオブジェクトという
状態監視インスタンスの導入が利用されている。
今回はこのDeferredオブジェクトを用いて
遅延処理の実装を行う。
ゴール
Alamofireの通信終了後に
意図した処理を実行する。
方針
Deferredオブジェクトを使う。
Deferredオブジェクトは処理が
進行中/完了後/異常終了のどの状態に
あるのかを監視するオブジェクトとなっている。
通信が終了したタイミングで
状態を"完了後"とすれば、
その後に続けたい処理を書くことで
コールバックを使いながらも{通信}→{意図した処理}の
順で実装できる。
javascript, 特にjQueryではajaxとともに
よく使われるオブジェクトである。
実装内容
RokkinCat/Swift-Promises*3にあった
Classesフォルダ以下の、Promise.swiftを
アプリソースコードに追加。
Promises in Swift*4の特に下記のcode1を参考にしながら
func callAPI() -> Promise
という
関数を実装した。
func uploadFile() -> Promise {
let p = Promise.defer()
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
{
let success = self.actualFileUpload()
if !success {
p.reject()
}
dispatch_async(dispatch_get_main_queue(), {
p.resolve()()
})
})
return p
}
...
let deferred = Deferred()
...
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
callAPI().then{(value) -> () in
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
self.tableView.reloadData()
}
}
func callAPI() -> Promise{
let promise = deferred.promise
Alamofire.request(CRUDRouter.Read(routerName: "users/1/favorites", id: 0))
.responseJSON { (_, _, JSON, _) in
var arr : NSArray = JSON!["comment"]!! as NSArray
for a in arr{
self.items.append(a["comment"]!! as String)
}
self.deferred.resolve("end API calling")
}
return promise
}
実装結果
下記のようなテーブルに
文章の追加された画面が、
画面表示後0.2秒程度で表示されるようになった。
Deferredを実装していない時には、
タブを行き来して2回目に表示したときに
初めて表示されていた。
(TableViewの実装方法については
ここでは割愛します。)
これにより、Deferredを使うことで
正しく通信→処理の順に
実装できることが確かめられた。
表示されているテキストは自前の
バックエンドに対してHTTP Requestを
飛ばした結果となっている。
まとめ
本記事ではDeferredを用いた処理遅延を実装してAlamofireの通信後に意図した処理を実行させた。
むっちゃ頑張った。
SwiftとJavascriptは似ているのではないかという気もしてきた。
JSONのAPIをコールする実装を紹介する記事[*5]
(http://qiita.com/yonell/items/c5432207868fa2d5cfc9)は
あったが、Alamofireの方が
API周りを簡潔に書けるため、こちらのほうが
コストの低い実装となると思う。
また、他のDeferredオブジェクトの実装[*6](http://qiita.com/xxxAIRINxxx/items/ba56c9bf51dde79157a
4)もありえたが、
今回はすぐに見つかったもので実装した。
参考文献
- Alamofire/Alamofire
- [Swift 1.1] swiftで api を叩いて、JSONをパースして、表示させる方法 (xcodeは6.1, iOSは8.1)
- RokkinCat/Swift-Promises
- Promises in Swift
- [Swift]APIで取得したJSONをswiftyJSONでパースして、天気情報をUITableViewで表示。お天気アプリを作ってみる。
- [【iOS】 コールバック地獄から脱出する為のライブラリ一覧](http://qiita.com/xxxAIRINxxx/items/ba56c9bf51dde79157a