Skip to content

Commit 656c4b5

Browse files
committed
Reduce number of times to send updates to observers
1 parent 7c4e3bb commit 656c4b5

File tree

6 files changed

+198
-102
lines changed

6 files changed

+198
-102
lines changed

Sources/Atoms/Core/StoreContext.swift

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ internal struct StoreContext {
6161
else {
6262
let cache = makeNewCache(of: atom, for: key, override: override)
6363
notifyUpdateToObservers()
64-
checkRelease(for: key)
64+
65+
if checkRelease(for: key) {
66+
notifyUpdateToObservers()
67+
}
68+
6569
return cache.value
6670
}
6771
}
@@ -127,19 +131,14 @@ internal struct StoreContext {
127131
location: container.location,
128132
requiresObjectUpdate: requiresObjectUpdate,
129133
notifyUpdate: notifyUpdate
130-
) {
131-
let store = getStore()
132-
// Unsubscribe and release if it's no longer used.
133-
store.state.subscriptions[key]?.removeValue(forKey: container.key)
134-
135-
if !checkRelease(for: key) {
136-
notifyUpdateToObservers()
137-
}
138-
}
134+
)
139135
let isInserted = store.state.subscriptions[key, default: [:]].updateValue(subscription, forKey: container.key) == nil
140136

141137
// Register the subscription to both the store and the container.
142138
container.subscriptions[key] = subscription
139+
container.unsubscribe = { keys in
140+
unsubscribe(keys, for: container.key)
141+
}
143142

144143
if isInserted || cache == nil {
145144
notifyUpdateToObservers()
@@ -165,8 +164,12 @@ internal struct StoreContext {
165164
if let cache = lookupCache(of: atom, for: key) {
166165
update(atom: atom, for: key, value: value, cache: cache, order: .newValue)
167166
}
167+
else {
168+
// Release the temporarily created state.
169+
// Do not notify update to observers here because refresh doesn't create a new cache.
170+
release(for: key)
171+
}
168172

169-
checkRelease(for: key)
170173
return value
171174
}
172175

@@ -185,7 +188,9 @@ internal struct StoreContext {
185188
func unwatch(_ atom: some Atom, container: SubscriptionContainer.Wrapper) {
186189
let override = lookupOverride(of: atom)
187190
let key = AtomKey(atom, overrideScopeKey: override?.scopeKey)
188-
container.subscriptions.removeValue(forKey: key)?.unsubscribe()
191+
192+
container.subscriptions.removeValue(forKey: key)
193+
unsubscribe([key], for: container.key)
189194
}
190195

191196
@usableFromInline
@@ -222,7 +227,7 @@ internal struct StoreContext {
222227

223228
// Release dependencies that are no longer dependent.
224229
if let dependencies = obsoletedDependencies[key] {
225-
checkReleaseDependencies(dependencies, for: key)
230+
detach(key, fromDependencies: dependencies)
226231
}
227232

228233
// Notify updates only for the subscriptions of restored atoms.
@@ -232,6 +237,8 @@ internal struct StoreContext {
232237
}
233238
}
234239
}
240+
241+
notifyUpdateToObservers()
235242
}
236243
}
237244

@@ -266,8 +273,10 @@ private extension StoreContext {
266273
let dependencies = store.graph.dependencies[key] ?? []
267274
let obsoletedDependencies = oldDependencies.subtracting(dependencies)
268275

269-
// Check if the dependencies that are no longer used and release them if possible.
270-
checkReleaseDependencies(obsoletedDependencies, for: key)
276+
if !obsoletedDependencies.isEmpty {
277+
detach(key, fromDependencies: obsoletedDependencies)
278+
notifyUpdateToObservers()
279+
}
271280
}
272281

273282
// Register the transaction state so it can be terminated from anywhere.
@@ -357,6 +366,17 @@ private extension StoreContext {
357366
}
358367
}
359368

369+
func unsubscribe(_ keys: [AtomKey], for subscriptionKey: SubscriptionKey) {
370+
let store = getStore()
371+
372+
for key in keys {
373+
store.state.subscriptions[key]?.removeValue(forKey: subscriptionKey)
374+
checkRelease(for: key)
375+
}
376+
377+
notifyUpdateToObservers()
378+
}
379+
360380
func invalidate(for key: AtomKey) -> Set<AtomKey> {
361381
let store = getStore()
362382

@@ -377,11 +397,7 @@ private extension StoreContext {
377397
store.state.states.removeValue(forKey: key)
378398
store.state.subscriptions.removeValue(forKey: key)
379399

380-
// Check if the dependencies are releasable.
381-
checkReleaseDependencies(dependencies, for: key)
382-
383-
// Notify release.
384-
notifyUpdateToObservers()
400+
detach(key, fromDependencies: dependencies)
385401
}
386402

387403
@discardableResult
@@ -405,10 +421,9 @@ private extension StoreContext {
405421
return true
406422
}
407423

408-
func checkReleaseDependencies(_ dependencies: Set<AtomKey>, for key: AtomKey) {
424+
func detach(_ key: AtomKey, fromDependencies dependencies: Set<AtomKey>) {
409425
let store = getStore()
410426

411-
// Recursively release dependencies while unlinking the dependent.
412427
for dependency in dependencies {
413428
store.graph.children[dependency]?.remove(key)
414429
checkRelease(for: dependency)
@@ -481,6 +496,7 @@ private extension StoreContext {
481496

482497
// Release the invalid registration as a fallback.
483498
release(for: key)
499+
notifyUpdateToObservers()
484500
return makeState()
485501
}
486502

@@ -510,6 +526,7 @@ private extension StoreContext {
510526

511527
// Release the invalid registration as a fallback.
512528
release(for: key)
529+
notifyUpdateToObservers()
513530
return nil
514531
}
515532

Sources/Atoms/Core/Subscription.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,4 @@ internal struct Subscription {
33
let location: SourceLocation
44
let requiresObjectUpdate: Bool
55
let notifyUpdate: () -> Void
6-
let unsubscribe: () -> Void
76
}

Sources/Atoms/Core/SubscriptionContainer.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22
@MainActor
33
internal final class SubscriptionContainer {
44
private var subscriptions = [AtomKey: Subscription]()
5+
private var unsubscribe: (([AtomKey]) -> Void)?
56
private let token = SubscriptionKey.Token()
67

78
nonisolated init() {}
89

910
deinit {
10-
for subscription in ContiguousArray(subscriptions.values) {
11-
subscription.unsubscribe()
12-
}
11+
unsubscribe?(Array(subscriptions.keys))
1312
}
1413

1514
func wrapper(location: SourceLocation) -> Wrapper {
@@ -31,6 +30,11 @@ internal extension SubscriptionContainer {
3130
nonmutating set { container?.subscriptions = newValue }
3231
}
3332

33+
var unsubscribe: (([AtomKey]) -> Void)? {
34+
get { container?.unsubscribe }
35+
nonmutating set { container?.unsubscribe = newValue }
36+
}
37+
3438
init(
3539
_ container: SubscriptionContainer,
3640
token: SubscriptionKey.Token,

0 commit comments

Comments
 (0)