Skip to content

Commit 0cd6e35

Browse files
authored
Reduce process redundancy (#71)
1 parent e2f1076 commit 0cd6e35

File tree

6 files changed

+213
-144
lines changed

6 files changed

+213
-144
lines changed

Sources/Atoms/Core/StoreContext.swift

Lines changed: 43 additions & 25 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
}
@@ -73,7 +77,6 @@ internal struct StoreContext {
7377

7478
if let cache = lookupCache(of: atom, for: key) {
7579
update(atom: atom, for: key, value: value, cache: cache, order: .newValue)
76-
checkRelease(for: key)
7780
}
7881
}
7982

@@ -86,7 +89,6 @@ internal struct StoreContext {
8689
var value = cache.value
8790
body(&value)
8891
update(atom: atom, for: key, value: value, cache: cache, order: .newValue)
89-
checkRelease(for: key)
9092
}
9193
}
9294

@@ -129,17 +131,14 @@ internal struct StoreContext {
129131
location: container.location,
130132
requiresObjectUpdate: requiresObjectUpdate,
131133
notifyUpdate: notifyUpdate
132-
) {
133-
let store = getStore()
134-
// Unsubscribe and release if it's no longer used.
135-
store.state.subscriptions[key]?.removeValue(forKey: container.key)
136-
notifyUpdateToObservers()
137-
checkRelease(for: key)
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

@@ -178,15 +181,16 @@ internal struct StoreContext {
178181
if let cache = lookupCache(of: atom, for: key) {
179182
let newCache = makeNewCache(of: atom, for: key, override: override)
180183
update(atom: atom, for: key, value: newCache.value, cache: cache, order: .newValue)
181-
checkRelease(for: key)
182184
}
183185
}
184186

185187
@usableFromInline
186188
func unwatch(_ atom: some Atom, container: SubscriptionContainer.Wrapper) {
187189
let override = lookupOverride(of: atom)
188190
let key = AtomKey(atom, overrideScopeKey: override?.scopeKey)
189-
container.subscriptions.removeValue(forKey: key)?.unsubscribe()
191+
192+
container.subscriptions.removeValue(forKey: key)
193+
unsubscribe([key], for: container.key)
190194
}
191195

192196
@usableFromInline
@@ -223,7 +227,7 @@ internal struct StoreContext {
223227

224228
// Release dependencies that are no longer dependent.
225229
if let dependencies = obsoletedDependencies[key] {
226-
checkReleaseDependencies(dependencies, for: key)
230+
detach(key, fromDependencies: dependencies)
227231
}
228232

229233
// Notify updates only for the subscriptions of restored atoms.
@@ -233,6 +237,8 @@ internal struct StoreContext {
233237
}
234238
}
235239
}
240+
241+
notifyUpdateToObservers()
236242
}
237243
}
238244

@@ -267,8 +273,10 @@ private extension StoreContext {
267273
let dependencies = store.graph.dependencies[key] ?? []
268274
let obsoletedDependencies = oldDependencies.subtracting(dependencies)
269275

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

274282
// Register the transaction state so it can be terminated from anywhere.
@@ -358,6 +366,17 @@ private extension StoreContext {
358366
}
359367
}
360368

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+
361380
func invalidate(for key: AtomKey) -> Set<AtomKey> {
362381
let store = getStore()
363382

@@ -378,14 +397,11 @@ private extension StoreContext {
378397
store.state.states.removeValue(forKey: key)
379398
store.state.subscriptions.removeValue(forKey: key)
380399

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

388-
func checkRelease(for key: AtomKey) {
403+
@discardableResult
404+
func checkRelease(for key: AtomKey) -> Bool {
389405
let store = getStore()
390406

391407
// The condition under which an atom may be released are as follows:
@@ -398,16 +414,16 @@ private extension StoreContext {
398414
lazy var shouldRelease = !shouldKeepAlive && isChildrenEmpty && isSubscriptionEmpty
399415

400416
guard shouldRelease else {
401-
return
417+
return false
402418
}
403419

404420
release(for: key)
421+
return true
405422
}
406423

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

410-
// Recursively release dependencies while unlinking the dependent.
411427
for dependency in dependencies {
412428
store.graph.children[dependency]?.remove(key)
413429
checkRelease(for: dependency)
@@ -480,6 +496,7 @@ private extension StoreContext {
480496

481497
// Release the invalid registration as a fallback.
482498
release(for: key)
499+
notifyUpdateToObservers()
483500
return makeState()
484501
}
485502

@@ -509,6 +526,7 @@ private extension StoreContext {
509526

510527
// Release the invalid registration as a fallback.
511528
release(for: key)
529+
notifyUpdateToObservers()
512530
return nil
513531
}
514532

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)