Created
May 16, 2020 08:46
-
-
Save marty-suzuki/cd2a97841777f2783fc2b1055c9ec445 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Playgroundでの実行を想定しているのでCombineを利用 | |
import Combine | |
// MARK: - CombineをRxSwiftのように定義する | |
typealias Observable<T> = AnyPublisher<T, Error> | |
typealias PublishRelay<T> = PassthroughSubject<T, Never> | |
typealias BehaviorRelay<T> = CurrentValueSubject<T, Never> | |
typealias DisposeBag = [AnyCancellable] | |
/// valueのsetterを非公開にしたBehaviorRelay | |
struct ValueObservable<Element> { | |
private let relay: BehaviorRelay<Element> | |
var value: Element { | |
relay.value | |
} | |
init(_ relay: BehaviorRelay<Element>) { | |
self.relay = relay | |
} | |
func asObservable() -> Observable<Element> { | |
relay | |
.tryCatch { _ -> Observable<Element> in fatalError() } // 型合わせのために記載しているので実行されない | |
.eraseToAnyPublisher() | |
} | |
} | |
/// countを公開するprotocol | |
protocol CountStoreProtocol: AnyObject { | |
var count: ValueObservable<Int> { get } | |
} | |
// MARK: - App側の定義 | |
/// アプリ側で利用するBehaviorRelayのpropertyWrapper | |
@propertyWrapper | |
struct BehaviorWrapper<Element> { | |
let wrappedValue: ValueObservable<Element> | |
private let relay: BehaviorRelay<Element> | |
init(_ value: Element) { | |
self.relay = BehaviorRelay(value) | |
self.wrappedValue = ValueObservable(relay) | |
} | |
/// propertyとして定義されたclassやstruct内のみ、_〇〇でアクセスできる | |
func accept(_ event: Element) { | |
relay.send(event) | |
} | |
} | |
func app() { | |
final class CountStore: CountStoreProtocol { | |
@BehaviorWrapper(0) | |
var count: ValueObservable<Int> | |
private var disposeBag = DisposeBag() | |
init(countUp: PublishRelay<Void>, | |
countDown: PublishRelay<Void>) { | |
countUp.map { 1 } | |
.merge(with: countDown.map { -1 }) | |
.flatMap { [_count] value in | |
Just(_count.wrappedValue.value + value) | |
} | |
.sink(receiveValue: { [_count] value in | |
_count.accept(value) // このクラス内でのみaceptが呼び出しできる | |
}) | |
.store(in: &disposeBag) | |
} | |
} | |
let countUp = PublishRelay<Void>() | |
let countDown = PublishRelay<Void>() | |
let store = CountStore(countUp: countUp, countDown: countDown) | |
let disposable = store.count.asObservable() | |
.sink(receiveCompletion: { _ in }) { count in | |
print("subsscribe: \(count)") | |
} | |
defer { disposable.cancel() } | |
print("value: \(store.count.value)") | |
countUp.send(()) | |
print("value: \(store.count.value)") | |
countDown.send(()) | |
print("value: \(store.count.value)") | |
} | |
// MARK: - テスト側の定義 | |
@propertyWrapper | |
struct MockBehaviorWrapper<Element> { | |
/// - note: $〇〇(value)で状態を更新できる | |
var projectedValue: (Element) -> Void { | |
{ [relay] in relay.send($0) } | |
} | |
let wrappedValue: ValueObservable<Element> | |
private let relay: BehaviorRelay<Element> | |
init(_ value: Element) { | |
self.relay = BehaviorRelay(value) | |
self.wrappedValue = ValueObservable(relay) | |
} | |
} | |
func test() { | |
final class MockCountStore: CountStoreProtocol { | |
// アプリ側とは違うpropertyWrapperを採用したstructを利用して、振る舞いを変更する | |
@MockBehaviorWrapper(0) | |
var count: ValueObservable<Int> | |
} | |
let store = MockCountStore() | |
let disposable = store.count.asObservable() | |
.sink(receiveCompletion: { _ in }) { count in | |
print("subsscribe: \(count)") | |
} | |
defer { disposable.cancel() } | |
print("value: \(store.count.value)") | |
store.$count(1) | |
print("value: \(store.count.value)") | |
} | |
print("app:") | |
app() | |
print("\ntest:") | |
test() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment