Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save marty-suzuki/cd2a97841777f2783fc2b1055c9ec445 to your computer and use it in GitHub Desktop.
Save marty-suzuki/cd2a97841777f2783fc2b1055c9ec445 to your computer and use it in GitHub Desktop.
// 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