diff --git a/Sources/Verge/Library/StoreSubscription.swift b/Sources/Verge/Library/StoreSubscription.swift index 399d6a1b1f..d9e1c7443e 100644 --- a/Sources/Verge/Library/StoreSubscription.swift +++ b/Sources/Verge/Library/StoreSubscription.swift @@ -36,6 +36,7 @@ public final class StoreSubscription: Hashable, Cancellable, @unchecked Sendable guard wasCancelled.compareExchange(expected: false, desired: true, ordering: .relaxed).exchanged else { return } + storeCancellable?.dissociate(self) source.cancel() associatedStore = nil } @@ -113,7 +114,8 @@ public final class StoreStateSubscription: Hashable, Cancellable, @unchecked Sen private weak var storeCancellable: VergeAnyCancellable? private var associatedStore: (any StoreType)? private var associatedReferences: [AnyObject] = [] - private let onAction: (StoreStateSubscription, Action) -> Void + + private var onAction: ((StoreStateSubscription, Action) -> Void)? init( _ eventEmitterCancellable: EventEmitterCancellable, @@ -132,8 +134,9 @@ public final class StoreStateSubscription: Hashable, Cancellable, @unchecked Sen } AtomicReferenceStorage.atomicLoad(at: &source, ordering: .relaxed).cancel() - + storeCancellable?.dissociate(self) associatedStore = nil + onAction = nil } func cancelSubscription() { @@ -174,7 +177,7 @@ public final class StoreStateSubscription: Hashable, Cancellable, @unchecked Sen return } - onAction(self, .suspend) + onAction?(self, .suspend) } @@ -197,7 +200,7 @@ public final class StoreStateSubscription: Hashable, Cancellable, @unchecked Sen return } - onAction(self, .resume) + onAction?(self, .resume) } func associate(store: some StoreType) -> StoreStateSubscription { diff --git a/Sources/Verge/Library/VergeAnyCancellable.swift b/Sources/Verge/Library/VergeAnyCancellable.swift index de8d44d2e6..871ec7ebac 100644 --- a/Sources/Verge/Library/VergeAnyCancellable.swift +++ b/Sources/Verge/Library/VergeAnyCancellable.swift @@ -4,6 +4,24 @@ import Combine /// A typealias to `Set`. public typealias VergeAnyCancellables = Set +final class Reference: Equatable, Hashable { + + static func == (lhs: Reference, rhs: Reference) -> Bool { + lhs === rhs + } + + func hash(into hasher: inout Hasher) { + ObjectIdentifier(value).hash(into: &hasher) + } + + let value: AnyObject + + init(value: AnyObject) { + self.value = value + } + +} + /// A type-erasing cancellable object that executes a provided closure when canceled. /// An AnyCancellable instance automatically calls cancel() when deinitialized. /// To cancel depending owner, can be written following @@ -39,7 +57,7 @@ public final class VergeAnyCancellable: Hashable, Cancellable, @unchecked Sendab } private var actions: ContiguousArray<() -> Void>? = .init() - private var retainObjects: ContiguousArray = .init() + private var retainObjects: Set = .init() public init() { } @@ -65,11 +83,27 @@ public final class VergeAnyCancellable: Hashable, Cancellable, @unchecked Sendab assert(!wasCancelled) - retainObjects.append(object) + retainObjects.insert(.init(value: object)) return self } + public func dissociate(_ object: AnyObject) { + + lock.lock() + + let target = retainObjects.remove(.init(value: object)) + + lock.unlock() + + guard let target else { + return + } + + withExtendedLifetime(target, {}) + + } + public func insert(_ cancellable: Cancellable) { lock.lock() @@ -102,21 +136,28 @@ public final class VergeAnyCancellable: Hashable, Cancellable, @unchecked Sendab public func cancel() { lock.lock() - defer { + + guard !wasCancelled else { lock.unlock() + return } - guard !wasCancelled else { return } wasCancelled = true + let _retainObjects = self.retainObjects retainObjects.removeAll() - - actions?.forEach { + + let _actions = self.actions + self.actions = nil + + lock.unlock() + + withExtendedLifetime(_retainObjects, {}) + + _actions?.forEach { $0() } - actions = nil - } }