0
1

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.

【SwiftChaining】イベントを送受信するプロトコル

Last updated at Posted at 2019-03-07

SwiftChainingの解説記事その6です。

SwiftChainingとは何か?というのは、こちらの最初の記事を参考にしてください。

今までの記事ではSwiftChainingで用意されたクラスを使用して、バインディングする方法を紹介してきました。

これまでそれら用意されたクラスに関して「監視対象」とか「値を返せる・返せない」といった表現で説明をしていたのですが、実際のところは何のプロトコルに適合しているかという話になります。今回はそのプロトコルについて解説します。

プロトコル

SwiftChainingでイベントの送受信してバインディングできるようにするために用意しているプロトコルがあります。

  • Chainable
  • Sendable
  • Fetchable
  • Syncable (Sendable & Fetchable)
  • Receivable

独自に作ったクラスでも、これらのプロトコルに適合させることでSwiftChainingでバインディング対象として扱うことができます。

Chainable

クラスを監視対象とするための基本となるプロトコルです。定義は以下のようになっています。

public protocol Chainable: class {
    // 送信するイベントの型
    associatedtype ChainValue
    // chain()で返す型(あまり気にしなくて良い)
    typealias FirstChain = Chain<ChainValue, Self>
}

extension Chainable {
    // バインディング処理の構築を開始する(実装済。そのまま使う)
    public func chain() -> FirstChain { ... }
    // Observerに保持させるためのオブジェクトを返す(実装済み。そのまま使う)
    public func retain() -> Retainer<Self> { ... }
}

Chainableだけに適合させてもchain()が呼べてバインディング処理が書けるだけの状態です。イベントを送信できるようにするためには、以下のSendableFetchableに適合させる必要があります。

Sendable

SendableChainableを継承しており、適合させるとイベントの送信ができます。

定義は以下のような感じです。

public protocol Sendable: Chainable {
}

extension Sendable {
    public func broadcast(value: ChainValue) { ... }
}

実装例として、Intを送ることに限定したNotifierを書いてみると、このようになります。

class IntNotifier: Sendable {
    typealias ChainValue = Int
    
    func notify(value: Int) {
        self.broadcast(value: value)
    }
}

イベントとして送る値をChainValueで定義し、イベントを送信するときはbroadcast(value:)を呼びます。必要な実装はこれだけです。

IntNotifierは、以下のように使うことができます。

let notifier = IntNotifier()

// chain()を呼べるので、イベントが送信された時に実行される処理を構築できる
let observer = notifier.chain().do { print($0) }.end()

// 値が送信されてdoが実行され、1がログに出力される
notifier.notify(value: 1)

Fetchable

FetchableChainableを継承しており、適合させるとsync()が呼ばれた時に値を引き出して送信することができます。

定義は以下のようになっています。

public protocol Fetchable: Chainable {
    // 送信する値を返す(要実装)
    func fetchedValue() -> ChainValue
    // 値が返せるかをBoolで返す(必要に応じて実装)
    func canFetch() -> Bool
}

extension Fetchable {
    public func canFetch() -> Bool {
        // デフォルトではtrueを返す
        return true
    }
}

fetchedValue()を必ず実装する必要があります。返り値に返すのはsync()を呼んだ時に送信される値です。

fetchedValue()で値を引き出せないことがある場合にcanFetch()falseを返すとfetchedValue()は呼ばれません。デフォルトはtrueなので、必要があれば実装をすると良いでしょう。

こちらも実装例として、Intを返すIntFetcherを書いてみるとこのようになります。

class IntFetcher: Fetchable {
    typealias ChainValue = Int
    
    var value: Int = 0
    
    func fetchedValue() -> Int {
        return self.value
    }
}

以下のように使うことができます。

let fetcher = IntFetcher()

// sync()で送信され0がログに出力される
let observer = fetcher.chain().do { print($0) }.sync()

sync()を呼んだタイミングでfetchedValue()で返した値がイベントとして送信されます。

ただ、今の所sync()を呼んだ時にしか動作しないので、Fetchableだけに適合してもあまり使い所がないと思います。実際にはSendableと組み合わせて使うことになるでしょう。

Syncable

Syncableは、SendableFetchableを組み合わせてtypealiasで定義した型です。

public typealias Syncable = Fetchable & Sendable

Fetchable & Sendableと直接書いても良いのですが、よく使うので組み合わせたものを作っておいている感じです。

値が変更された時の送信と、監視を開始した時の値の取得を組み合わせていて、値の同期に使えるということでSyncableという名前にしています。

例として、Intを保持して監視対象とするクラスを作ると以下のようになります。

class IntHolder: Syncable {
    typealias ChainValue = Int
    
    var value: Int = 0 {
        didSet {
            if self.value != oldValue {
                self.broadcast(value: self.value)
            }
        }
    }
    
    func fetchedValue() -> Int {
        return self.value
    }
}

使い方は以下のようになります。

let holder = IntHolder()

// sync()で送信され0がログに出力される
let observer = holder.chain().do { print($0) }.sync()

// 値が変更されてイベントが送信されたので1が出力される
holder.value = 1

Receivable

Receivableに適合したクラスは、sendTo()でイベントを受信することができます。

定義は以下の通りです。

public typealias Receivable = ValueReceivable & ReceiveReferencable

public protocol ValueReceivable: class {
    // 受信するイベントの型
    associatedtype ReceiveValue
    // 受信した時の処理(要実装)
    func receive(value: ReceiveValue)
}

// 内部的な処理に使う
public protocol ReceiveReferencable {
    associatedtype ReceiveObject: ValueReceivable = Self
    
    func reference() -> Reference<ReceiveObject>
}

受け取るイベントの型をReceiveValueで定義し、receive(value:)を実装する必要があります。イベントで送信されてきた値がvalueに入ってきますので、受け取った時の処理を書いてください。

例として、Syncableの例のIntHolderをさらに拡張してみたいと思います。

class IntHolder: Syncable, Receivable {
    typealias ChainValue = Int
    typealias ReceiveValue = Int
    
    var value: Int {
        didSet {
            if self.value != oldValue {
                self.broadcast(value: self.value)
            }
        }
    }
    
    init(_ initial: Int) {
        self.value = initial
    }
    
    func fetchedValue() -> Int {
        return self.value
    }
    
    func receive(value: Int) {
        self.value = value
    }
}

以下のように使うことができます。

let sender = IntHolder(0)
let receiver = IntHolder(1)

// syncで値が同期され、receiverは0になる
let observer = sender.chain().sendTo(receiver).sync()

// 0がログに出力される
print(receiver.value)

// イベントが送信され、receiverは2になる
sender.value = 2

// 2がログに出力される
print(receiver.value)
0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?