SwiftChainingというデータバインディングのライブラリを作ってみました。それなりに使い物になってきた気がするので、どのようなものか紹介しようと思います。
まだ破壊的な変更は加わる可能性はありますが、その点はご了承ください。Qiitaの記事はライブラリの変更に合わせて随時更新をしていく予定です。
SwiftChainingとは?
Swiftで書かれたデータバインディングをするライブラリです。監視対象となるオブジェクトからメソッドチェーンで処理をつなげて書いて、別のオブジェクトに値に反映させるということができます。
CocoaPodsでインストールできます。モジュール名はSwiftを含まずChaining
です。
pod 'Chaining'
なぜ作ったのか?
iOS開発でデータバインディングできるライブラリは何かないかな?と探してみると、だいたいRxSwiftのようなReactive Extensionsをルーツとしたライブラリに行き当たります。でも、個人的には非同期処理を簡単に書きたいわけではなく、単にバインディングを便利にしたいだけなので、それにしてはちょっと複雑すぎる印象を受けます。
とはいえ、ライブラリを使わないと何かしらの通知を受け取るのにNotificationCenterやKVO、デリゲートなどいろいろ書き方も違うこともありますし、完璧に値を同期させるのも大変です。
そこで、統一的な書き方でメソッドチェーンを使って値を伝播させることできるものがあれば十分だろうと考え、自分でライブラリを作りました。
簡単な使い方
どのような感じで使えるものなのか簡単なコードをお見せします。
値を同期してみる
2箇所にある値をバインディングして同期してみたいと思います。以下のようなコードになります。
import UIKit
import Chaining
let a = ValueHolder<Int>(0)
let b = ValueHolder<Int>(1)
// aは0、bは1
let observer: AnyObserver = a.chain().sendTo(b).sync()
// aとbは両方0
a.value = 2
// aとbは両方2
observer.invalidate()
a.value = 3
// aは3で、bは2のまま
解説
バインディングするための値を扱うのにValueHolder
というクラスを用意しています。ValueHolder
は、値を保持していて、監視する方にも監視される方にもなるクラスです。ジェネリクスで保持する型を指定できます。
let a = ValueHolder<Int>(0)
let b = ValueHolder<Int>(1) // <Int>は省略してValueHolder(1)という書き方もできる
// aは0、bは1を保持している
print("\(a.value) / \(b.value)") // -> "0 / 1"
Int
を保持するValueHolder
を2つの生成しました。この時点ではそれぞれ0
と1
という別の値を持っています。
chain()
という関数を呼ぶと、ValueHolder
の持つ値が変更された時に呼ばれるバインディング処理をメソッドチェーンで記述していくことができます。
let observer: AnyObserver = a.chain().sendTo(b).sync()
// aからbに値が同期され両方0になっている
print("\(a.value) / \(b.value)") // -> "0 / 0"
今回はa
の持つ値を何もせずそのままb
に送りたいので、sendTo()
という関数にb
を渡します。最後にsync()
を呼ぶと処理の流れが確定し、1度a
から値が送信された上でAnyObserver
という型のObserverオブジェクトを返します。
Observerはchain()
の後に書かれたバインディング処理の一連の流れを保持していて、生存している間はa
の変更を監視し続けます。a
の値を変更すればb
に値が反映されます。
a.value = 2
// holder1の変更がholder2に反映され両方2になっている
print("\(a.value) / \(b.value)") // -> "2 / 2"
Observerを破棄するか、あるいはinvalidate()
関数を呼ぶことで監視が止まります。
observer.invalidate()
a.value = 3
// 監視は終わったのでbは2のまま
print("\(a.value) / \(b.value)") // -> "3 / 2"
単に値を送ってみる
値と値をバインディングをするという感じではなく、NotificationCenter
のように単に値を一方的に送ることもできるので書いてみます。以下のようなコードになります。
let notifier = Notifier<Int>()
let observer = notifier.chain().do { value in print(value) }.end()
notifier.notify(value: 1)
解説
単に値を送信するクラスとしてNotifier
を用意しています。送る値の型をジェネリクスで指定できます。
let notifier = Notifier<Int>()
こちらも同じくchain()
メソッドでバインディング処理の記述を始めます。
let observer = notifier.chain().do { value in print(value) }.end()
Notifier
はValueHolder
と違って値を保持しているわけではないので、sync()
ではなくend()
という関数で処理の流れを確定させます。end
を呼んだ時点では何も送る値はないので、何も実行されません。
chain()
の次にdo
関数がありますが、これは単にクロージャで処理を差し込むものです。
Notifier
はnotify(value:)
関数で値を送信することができます。
notifier.notify(value: 1) // -> "1"
値が送信されるとdo
の処理が実行され、この場合1
がログに出力されます。
このNotifier
の例ではdo
関数で値を受け取るようにしましたが、sendTo()
でValueHolder
が受け取ることもできます。それらを繋げて書いて、2つ以上の処理を続けて行うこともできます。
以上が簡単な使い方です。
この記事に書いたもの以外にも色々とクラスとか機能はあるのですが、それは以下の記事を参照してください。
関連記事一覧
【SwiftChaining】 UIとバインディングする
【SwiftChaining】 NotificationCenterからの通知を受け取る
【SwiftChaining】 処理を構築する関数
【SwiftChaining】 処理を確定する関数
【SwiftChaining】 イベントを送受信するプロトコル
【SwiftChaining】 オブジェクトの保持