LoginSignup
8
11

More than 5 years have passed since last update.

【SwiftChaining】 Swiftでデータバインディングするライブラリを作った

Last updated at Posted at 2019-02-23

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つの生成しました。この時点ではそれぞれ01という別の値を持っています。

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()

NotifierValueHolderと違って値を保持しているわけではないので、sync()ではなくend()という関数で処理の流れを確定させます。endを呼んだ時点では何も送る値はないので、何も実行されません。

chain()の次にdo関数がありますが、これは単にクロージャで処理を差し込むものです。

Notifiernotify(value:)関数で値を送信することができます。

notifier.notify(value: 1) // -> "1"

値が送信されるとdoの処理が実行され、この場合1がログに出力されます。

このNotifierの例ではdo関数で値を受け取るようにしましたが、sendTo()ValueHolderが受け取ることもできます。それらを繋げて書いて、2つ以上の処理を続けて行うこともできます。

以上が簡単な使い方です。

この記事に書いたもの以外にも色々とクラスとか機能はあるのですが、それは以下の記事を参照してください。

関連記事一覧

【SwiftChaining】 UIとバインディングする
【SwiftChaining】 NotificationCenterからの通知を受け取る
【SwiftChaining】 処理を構築する関数
【SwiftChaining】 処理を確定する関数
【SwiftChaining】 イベントを送受信するプロトコル
【SwiftChaining】 オブジェクトの保持

8
11
1

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
8
11