From 4fe1c83ae1f6e9a6bd41f3617f74a1f77c74a441 Mon Sep 17 00:00:00 2001 From: dankinsoid <30962149+dankinsoid@users.noreply.github.com> Date: Tue, 12 Mar 2024 13:12:34 +0300 Subject: [PATCH] 0.21.0 --- README.md | 2 +- Sources/VDStore/Store.swift | 4 +- Sources/VDStore/Utils/StoreBox.swift | 83 ++++++++++++++++--------- Tests/VDStoreTests/VDStoreTests.swift | 89 ++++++++++++++++++++++++--- 4 files changed, 138 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index a9624b9..30198de 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ import PackageDescription let package = Package( name: "SomeProject", dependencies: [ - .package(url: "https://github.com/dankinsoid/VDStore.git", from: "0.20.0") + .package(url: "https://github.com/dankinsoid/VDStore.git", from: "0.21.0") ], targets: [ .target(name: "SomeProject", dependencies: ["VDStore"]) diff --git a/Sources/VDStore/Store.swift b/Sources/VDStore/Store.swift index 4f0e76b..31d8510 100644 --- a/Sources/VDStore/Store.swift +++ b/Sources/VDStore/Store.swift @@ -299,8 +299,8 @@ public struct Store { /// Suspends the store from updating the UI until the block returns. public func update(_ update: () throws -> T) rethrows -> T { - defer { box.afterUpdate() } - box.beforeUpdate() + defer { box.endUpdate() } + box.startUpdate() let result = try update() return result } diff --git a/Sources/VDStore/Utils/StoreBox.swift b/Sources/VDStore/Utils/StoreBox.swift index 0a92ba4..8477ae6 100644 --- a/Sources/VDStore/Utils/StoreBox.swift +++ b/Sources/VDStore/Utils/StoreBox.swift @@ -11,8 +11,8 @@ struct StoreBox: Publisher { } let willSet: AnyPublisher - let beforeUpdate: () -> Void - let afterUpdate: () -> Void + let startUpdate: () -> Void + let endUpdate: () -> Void private let getter: () -> Output private let setter: (Output) -> Void private let valuePublisher: AnyPublisher @@ -23,8 +23,8 @@ struct StoreBox: Publisher { valuePublisher = rootBox.eraseToAnyPublisher() getter = { rootBox.state } setter = { rootBox.state = $0 } - beforeUpdate = rootBox.beforeUpdate - afterUpdate = rootBox.afterUpdate + startUpdate = rootBox.startUpdate + endUpdate = rootBox.endUpdate } init( @@ -40,8 +40,8 @@ struct StoreBox: Publisher { set(&state, $0) parent.setter(state) } - beforeUpdate = parent.beforeUpdate - afterUpdate = parent.afterUpdate + startUpdate = parent.startUpdate + endUpdate = parent.endUpdate } func receive(subscriber: S) where S: Subscriber, Never == S.Failure, Output == S.Input { @@ -55,56 +55,79 @@ private final class StoreRootBox: Publisher { typealias Failure = Never var state: State { - get { subject.value } - set { - if suspendAllSyncStoreUpdates, updatesCounter == 0 { - suspendSyncUpdates() - } else if updatesCounter == 0 { - willSet.send() + willSet { + if updatesCounter == 0 { + if suspendAllSyncStoreUpdates { + if asyncUpdatesCounter == 0 { + suspendSyncUpdates() + } + } else { + willSet.send() + } + } + } + didSet { + if updatesCounter == 0, asyncUpdatesCounter == 0 { + didSet.send() } - subject.value = newValue } } - var willSetPublisher: AnyPublisher { publisher(willSet) } + var willSetPublisher: AnyPublisher { + willSet.eraseToAnyPublisher() + } private var updatesCounter = 0 + private var asyncUpdatesCounter = 0 private let willSet = PassthroughSubject() - private let subject: CurrentValueSubject + private let didSet = PassthroughSubject() init(_ state: State) { - subject = CurrentValueSubject(state) + self.state = state } func receive(subscriber: S) where S: Subscriber, Never == S.Failure, Output == S.Input { - publisher(subject).receive(subscriber: subscriber) + didSet + .compactMap { [weak self] in self?.state } + .prepend(state) + .receive(subscriber: subscriber) } - private func publisher(_ publisher: P) -> AnyPublisher { - publisher.filter { [weak self] _ in - self?.updatesCounter == 0 + func startUpdate() { + if updatesCounter == 0, asyncUpdatesCounter == 0 { + willSet.send() + } + updatesCounter &+= 1 + } + + func endUpdate() { + updatesCounter &-= 1 + guard updatesCounter == 0 else { return } + didSet.send() + + if asyncUpdatesCounter > 0 { + willSet.send() } - .eraseToAnyPublisher() } private func suspendSyncUpdates() { - beforeUpdate() + startAsyncUpdate() DispatchQueue.main.async { [self] in - afterUpdate() + endAsyncUpdate() } } - func beforeUpdate() { - if updatesCounter == 0 { + private func startAsyncUpdate() { + if asyncUpdatesCounter == 0 { willSet.send() } - updatesCounter &+= 1 + asyncUpdatesCounter &+= 1 } - func afterUpdate() { - updatesCounter &-= 1 - if updatesCounter == 0 { - subject.value = state + private func endAsyncUpdate() { + asyncUpdatesCounter &-= 1 + if asyncUpdatesCounter == 0 { + didSet.send() } } } diff --git a/Tests/VDStoreTests/VDStoreTests.swift b/Tests/VDStoreTests/VDStoreTests.swift index 29103e4..38a8692 100644 --- a/Tests/VDStoreTests/VDStoreTests.swift +++ b/Tests/VDStoreTests/VDStoreTests.swift @@ -96,14 +96,14 @@ final class VDStoreTests: XCTestCase { let initialCounter = Counter(counter: 0) let store = Store(initialCounter) let expectation = expectation(description: "State updated") - var bag = Set() store.publisher.sink { newState in if newState.counter == 1 { expectation.fulfill() + store.di.cancellableSet = [] } } - .store(in: &bag) + .store(in: &store.di.cancellableSet) store.add() await fulfillment(of: [expectation], timeout: 0.1) @@ -118,21 +118,30 @@ final class VDStoreTests: XCTestCase { func testNumberOfUpdates() async { let store = Store(Counter()) let publisher = store.publisher - var count = 0 + var updatesCount = 0 + var willSetCount = 0 let expectation = expectation(description: "Counter") - let cancellable = publisher + publisher .sink { i in - count += 1 + updatesCount += 1 if i.counter == 10 { expectation.fulfill() + store.di.cancellableSet = [] } } - cancellable.store(in: &store.di.cancellableSet) + .store(in: &store.di.cancellableSet) + store.willSet + .sink { _ in + willSetCount += 1 + } + .store(in: &store.di.cancellableSet) for _ in 0 ..< 10 { store.add() } + XCTAssertEqual(willSetCount, 1) + XCTAssertEqual(updatesCount, 1) await fulfillment(of: [expectation], timeout: 0.1) - XCTAssertEqual(count, 2) + XCTAssertEqual(updatesCount, 2) } func testOnChange() async { @@ -147,6 +156,72 @@ final class VDStoreTests: XCTestCase { await fulfillment(of: [expectation], timeout: 0.1) XCTAssertEqual(store.state.counter, 2) } + + func testSyncUpdateInAsyncUpdate() async { + let store = Store(Counter()) + let publisher = store.publisher + var updatesCount = 0 + var willSetCount = 0 + let expectation = expectation(description: "Counter") + publisher + .sink { i in + updatesCount += 1 + if i.counter == 10 { + expectation.fulfill() + store.di.cancellableSet = [] + } + } + .store(in: &store.di.cancellableSet) + store.willSet + .sink { _ in + willSetCount += 1 + } + .store(in: &store.di.cancellableSet) + store.add() + store.update { + for _ in 0 ..< 8 { + store.add() + } + } + XCTAssertEqual(updatesCount, 2) + store.add() + XCTAssertEqual(willSetCount, 2) + await fulfillment(of: [expectation], timeout: 0.1) + XCTAssertEqual(updatesCount, 3) + } + + func testAsyncUpdateInSyncUpdate() async { + let store = Store(Counter()) + let publisher = store.publisher + var updatesCount = 0 + var willSetCount = 0 + let expectation = expectation(description: "Counter") + publisher + .sink { i in + updatesCount += 1 + if i.counter == 10 { + expectation.fulfill() + store.di.cancellableSet = [] + } + } + .store(in: &store.di.cancellableSet) + store.willSet + .sink { _ in + willSetCount += 1 + } + .store(in: &store.di.cancellableSet) + store.update { + store.add() + for _ in 0 ..< 8 { + store.add() + } + store.add() + } + XCTAssertEqual(willSetCount, 1) + XCTAssertEqual(updatesCount, 2) + await fulfillment(of: [expectation], timeout: 0.1) + XCTAssertEqual(updatesCount, 2) + } #endif }