SwiftBondを使ってみての所感

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

[https://github.com/SwiftBond/Bond](https://github.com/SwiftBond/Bond)

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

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

## 細かなこと

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

```swift
group.name ->> nameTextField.dynText
```

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

```swift
group.name <->> nameTextField.dynText
```

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


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

```swift
var name = Dynamic<String>("")
```

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

```
name.value
```

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

## 宣言的に書くということ

`Bond`に限った話ではないと思いますが、こういった宣言的なプログラミングのスタイルってコードが１箇所に集中しやすいのかなと思いました。
今回も下のような(アプリからそのままもってきているので、わかりづらいのですが独自のヘルパークラスとかもまぎれてます。)、`UIViewController`の`viewDidLoad`にバインディングの記述をひたすら書いていくみたいな感じでした。

```swift
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`まわりはもう１つ個人的にしっくりこないことがあって、ちょっとした`UITableView`であれば

```swift
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
 
}
```

このような感じで書けて、`UITableViewDataSourceBond`が`UITableViewDataSource`を内包しているので、

```
    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.

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

## UIKitまわりのプロパティ

```swift
group.name ->> nameTextField.dynText
```

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

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

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

## queueまわりの話

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

```swift
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`リリースされたらまたどうするか考えようと思っています。