Posted at

SwiftBondを使ってみての所感

More than 3 years have passed since last update.

個人でつくっているiOSアプリ(swift)で、データバインディングのライブラリとしてBondをつかってみました。

https://github.com/SwiftBond/Bond

データバインディングのみの軽量なライブラリだから特にハマることもないだろうと思ったけれどそうでもなかったので、そういったことも含め個人的な感想を書いてみようと思います。Bondの基本的な使い方に関してはREADM.mdに書いてあるので割愛します。

注:あくまで現時点(2015/8)のBondについての話で、バージョンは3.10.0ものになります。


細かなこと


->>, <->>といったオペレータの書き方は直感的でいいかも

group.name ->> nameTextField.dynText

こんな感じで、groupモデルのnameという文字列のプロパティを、nameTextFieldtextプロパティにバインディングする。

group.name <->> nameTextField.dynText

<->>だと双方向バインディングになる(UITextFieldで入力した値がgroupモデルのnameプロパティにもバインディングされる)。


.valueで値にアクセスするのがめんどい

var name = Dynamic<String>("")

Bondではこんな感じで変数を定義して、例えばAPIにパラメータとして渡すために実際にその値を取得するときなんかは

name.value

と書く。これが個人的に少しめんどいのと、久しぶりに書くと一瞬忘れてることがある。


宣言的に書くということ

Bondに限った話ではないと思いますが、こういった宣言的なプログラミングのスタイルってコードが1箇所に集中しやすいのかなと思いました。

今回も下のような(アプリからそのままもってきているので、わかりづらいのですが独自のヘルパークラスとかもまぎれてます。)、UIViewControllerviewDidLoadにバインディングの記述をひたすら書いていくみたいな感じでした。

override func viewDidLoad() {

/** 省略... */

// アイテムが複数ないときはソートボタンを無効にする
map(self.list.dynCount) { count in
return self.list.count > 1
} ->> sortButton.dynEnabled

// 投稿可能文字数のときのみ投稿ボタンを有効にする
map(self.postTextView.dynText) { string in
return (count(string) > 0 && count(string) < 140)
} ->> postButton.dynEnabled

// アイデア一覧テーブル
self.list.map { [unowned self] (idea: Idea) -> IdeaTableViewCell in
let cell = IdeaTableViewCell.getView("IdeaTableViewCell") as! IdeaTableViewCell
cell.selectionStyle = UITableViewCellSelectionStyle.None
idea.identifier ->> cell.identifier
idea.postUser.profile.displayName ->> cell.posterLabel.dynText
idea.likeCount <->> cell.likeCount
idea.content ->> cell.contentLabel!.dynText
map(idea.createdAt) { dateString in
return DateHelper.getDateString(dateString)
} ->> cell.dateLabel.dynText
return cell
} ->> self.tableViewDataSourceBond

}

テーブルビューのセルも上のようには書けるのですが、(少なくとも自分は)これまではセルにデータを引数としたメソッドを生やして、セル側のほうで処理をするような書き方をしていたので(=セル側に記述を分離)ちょっとコードの見通しが悪くて気になりました。ベストプラクティスあれば知りたい。


UITableViewまわりが意外に煩雑

UITableViewまわりはもう1つ個人的にしっくりこないことがあって、ちょっとしたUITableViewであれば

var tableViewDataSourceBond: UITableViewDataSourceBond<UITableViewCell>!

override func viewDidLoad() {

/** 省略... */

self.tableViewDataSourceBond = UITableViewDataSourceBond(tableView: self.ideaTableView)

self.list.map { [unowned self] (idea: Idea) -> IdeaTableViewCell in
let cell = IdeaTableViewCell.getView("IdeaTableViewCell") as! IdeaTableViewCell

// バインディング処理

return cell
} ->> self.tableViewDataSourceBond

}

このような感じで書けて、UITableViewDataSourceBondUITableViewDataSourceを内包しているので、

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

return list.count
}

などのUITableViewDataSourceのデリゲートメソッドは書かなくていい。

が、README.mdにあるように、


numberOfSectionsInTableView:

tableView:numberOfRowsInSection:
tableView:cellForRowAtIndexPath:

If you need to provide other information to the table view, you can have your class adhere to protocol UITableViewDataSource and implement methods you need. After that, set nextDataSource property of UITableViewDataSourceBond to your object.


上の3つのデリゲートメソッド以外が必要なときは、UITableViewDataSourceを実装してとのこと。今回はセクションのヘッダのタイトルを指定して表示するという仕様があったので、結局UITableViewDataSourceをプロトコルとしてもたなくちゃいけなかったりで、ちょっとゴチャゴチャな実装になってしまいました。


UIKitまわりのプロパティ

group.name ->> nameTextField.dynText

このdynTextなどの、dynというprefixのついた各々のUIKitのプロパティはBond側で1つ1つ実装しているもので、当然すべてのプロパティを網羅しているわけではないので、場合によっては対応していないものに出くわすことがあるかもしれません。自分はUINavigationBarbarTintColorを動的に変える必要があったので、仕方なくPR投げてみました。

https://github.com/SwiftBond/Bond/pull/127

後述するように、これを書いている時点ではとくにマージもコメントもされていません。


queueまわりの話

さきほどからテーブルビューのセルを例に出していますが、以下のようにURL文字列をセル上のUIImageViewにバインディングしたいということもあると思います。

map(some.image_url) { urlString in

var url = NSURL(string: urlString)
if let existUrl = url {
var data = NSData(contentsOfURL: existUrl)
if let existData = data {
return UIImage(data: existData)!
}
}
return UIImage()
} ->> cell.avatarImageView.dynImage

が、このままだと、map内のクロージャの処理はメインスレッドで実行されてしまうため、よくある「テーブルビューのカタつく問題」になってしまいます。

deliverという値を反映させるときにキュー(スレッド)を指定するメソッドも用意されているのですが、上記の例とは利用ケースが異なるものです。

こちらもPR投げてみました。

https://github.com/SwiftBond/Bond/pull/133

が、作者からは「今はswift 2に向けて移行中なので現行の安定バージョンはいじりたくない」といった旨のコメントいただきました。さきほどのUINavigationBarの件もこれかもしれません。


Bond 4.0.0への移行

ということで、これもBondに限った話ではないと思いますが、現時点(2015/8)ではSwift 2に対応したバージョンに移行中(Bond 4.0,0)とのことです。言われてみれば至極当然の話ですが、今はライブラリの選定だったり悩ましい時期ですね。

ちなみにさきほどのmap内のクロージャの処理の件については、

some.image_url

.deliverOn(Queue.Background)
.map { url in
// download and return UIImage
}
.deliverOn(Queue.Main)
.bindTo(cell.avatarImageView.bnd_image)

と書けるようになるとのことです。作者さんがPRに答えてくれています。

とりあえず自分はforkした自分のレポジトリのものを使っていって、また4.0.0リリースされたらまたどうするか考えようと思っています。