Skip to content

Commit

Permalink
0.21.0
Browse files Browse the repository at this point in the history
  • Loading branch information
dankinsoid committed Mar 12, 2024
1 parent 666cdab commit 4fe1c83
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 40 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"])
Expand Down
4 changes: 2 additions & 2 deletions Sources/VDStore/Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,8 @@ public struct Store<State> {

/// Suspends the store from updating the UI until the block returns.
public func update<T>(_ update: () throws -> T) rethrows -> T {
defer { box.afterUpdate() }
box.beforeUpdate()
defer { box.endUpdate() }
box.startUpdate()
let result = try update()
return result
}
Expand Down
83 changes: 53 additions & 30 deletions Sources/VDStore/Utils/StoreBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ struct StoreBox<Output>: Publisher {
}

let willSet: AnyPublisher<Void, Never>
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<Output, Never>
Expand All @@ -23,8 +23,8 @@ struct StoreBox<Output>: Publisher {
valuePublisher = rootBox.eraseToAnyPublisher()
getter = { rootBox.state }
setter = { rootBox.state = $0 }
beforeUpdate = rootBox.beforeUpdate
afterUpdate = rootBox.afterUpdate
startUpdate = rootBox.startUpdate
endUpdate = rootBox.endUpdate
}

init<T>(
Expand All @@ -40,8 +40,8 @@ struct StoreBox<Output>: Publisher {
set(&state, $0)
parent.setter(state)
}
beforeUpdate = parent.beforeUpdate
afterUpdate = parent.afterUpdate
startUpdate = parent.startUpdate
endUpdate = parent.endUpdate
}

func receive<S>(subscriber: S) where S: Subscriber, Never == S.Failure, Output == S.Input {
Expand All @@ -55,56 +55,79 @@ private final class StoreRootBox<State>: 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<Void, Never> { publisher(willSet) }
var willSetPublisher: AnyPublisher<Void, Never> {
willSet.eraseToAnyPublisher()
}

private var updatesCounter = 0
private var asyncUpdatesCounter = 0
private let willSet = PassthroughSubject<Void, Never>()
private let subject: CurrentValueSubject<State, Never>
private let didSet = PassthroughSubject<Void, Never>()

init(_ state: State) {
subject = CurrentValueSubject(state)
self.state = state
}

func receive<S>(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<P: Publisher>(_ publisher: P) -> AnyPublisher<P.Output, P.Failure> {
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()
}
}
}
89 changes: 82 additions & 7 deletions Tests/VDStoreTests/VDStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<AnyCancellable>()

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)
Expand All @@ -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 {
Expand All @@ -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
}

Expand Down

0 comments on commit 4fe1c83

Please sign in to comment.