43
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SwiftBondを使ってみての所感

Posted at

個人でつくっている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まわりのプロパティ

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

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

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

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投げてみました。

が、作者からは「今は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リリースされたらまたどうするか考えようと思っています。

43
42
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
43
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?