From ba0173b8d4d61cdbf3b946e5b82657c5c0a38b5e Mon Sep 17 00:00:00 2001 From: pjechris Date: Sat, 10 Aug 2024 18:18:44 +0200 Subject: [PATCH] tech(registry): Add key parameter (#70) --- .../Observer/ObserverRegistry.swift | 50 +++++++++++++++---- fastlane/Fastfile | 2 +- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/Sources/CohesionKit/Observer/ObserverRegistry.swift b/Sources/CohesionKit/Observer/ObserverRegistry.swift index dcbc53a..fe8bd44 100644 --- a/Sources/CohesionKit/Observer/ObserverRegistry.swift +++ b/Sources/CohesionKit/Observer/ObserverRegistry.swift @@ -1,23 +1,41 @@ import Foundation +/// a unique hash identifying an object +typealias ObjectKey = Int + +extension ObjectKey { + init(of type: T.Type, id: Any) { + let key = "\(type)-\(id)" + + self.init(key.hashValue) + } + + init(_ node: EntityNode) { + self.init(of: T.self, id: node.hashValue) + } +} + /// Registers observers associated to an ``EntityNode``. /// The registry will handle notifying observers when a node is marked as changed class ObserverRegistry { - private typealias Hash = Int let queue: DispatchQueue /// registered observer handlers - private var handlers: [Hash: Set] = [:] + private var handlers: [ObjectKey: Set] = [:] /// nodes waiting for notifiying their observes about changes - private var pendingChanges: [Hash: AnyWeak] = [:] + private var pendingChanges: [ObjectKey: AnyWeak] = [:] init(queue: DispatchQueue? = nil) { self.queue = queue ?? DispatchQueue.main } + func addObserver(node: EntityNode, initial: Bool = false, onChange: @escaping (T) -> Void) -> Subscription { + addObserver(node: node, key: ObjectKey(node), initial: initial, onChange: onChange) + } + /// register an observer to observe changes on an entity node. Everytime `ObserverRegistry` is notified about changes /// to this node `onChange` will be called. - func addObserver(node: EntityNode, initial: Bool = false, onChange: @escaping (T) -> Void) -> Subscription { + func addObserver(node: EntityNode, key: ObjectKey, initial: Bool = false, onChange: @escaping (T) -> Void) -> Subscription { let handler = Handler { onChange($0.ref.value) } if initial { @@ -31,7 +49,7 @@ class ObserverRegistry { } } - return subscribeHandler(handler, for: node) + return subscribeHandler(handler, for: node, key: key) } /// Add an observer handler to multiple nodes. @@ -53,7 +71,7 @@ class ObserverRegistry { } } - let subscriptions = nodes.map { node in subscribeHandler(handler, for: node) } + let subscriptions = nodes.map { node in subscribeHandler(handler, for: node, key: ObjectKey(node)) } return Subscription { subscriptions.forEach { $0.unsubscribe() } @@ -62,11 +80,19 @@ class ObserverRegistry { /// Mark a node as changed. Observers won't be notified of the change until ``postChanges`` is called func enqueueChange(for node: EntityNode) { - pendingChanges[node.hashValue] = Weak(value: node) + enqueueChange(for: node, key: ObjectKey(node)) + } + + func enqueueChange(for node: EntityNode, key: ObjectKey) { + pendingChanges[key] = Weak(value: node) } func hasPendingChange(for node: EntityNode) -> Bool { - pendingChanges[node.hashValue] != nil + hasPendingChange(for: ObjectKey(node)) + } + + func hasPendingChange(for key: ObjectKey) -> Bool { + pendingChanges[key] != nil } /// Notify observers of all queued changes. Once notified pending changes are cleared out. @@ -97,12 +123,14 @@ class ObserverRegistry { } } - private func subscribeHandler(_ handler: Handler, for node: EntityNode) -> Subscription { - handlers[node.hashValue, default: []].insert(handler) + private func subscribeHandler(_ handler: Handler, for node: EntityNode, key: ObjectKey) -> Subscription { + handlers[key, default: []].insert(handler) // subscription keeps a strong ref to node, avoiding it from being released somehow while suscription is running return Subscription { [node] in - self.handlers[node.hashValue]?.remove(handler) + withExtendedLifetime(node) { } + + self.handlers[key]?.remove(handler) } } } diff --git a/fastlane/Fastfile b/fastlane/Fastfile index d06b43f..21fff87 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -28,7 +28,7 @@ platform :ios do scheme: "Example", configuration: "Debug", build: true, - destination: "platform=iOS Simulator,name=iPhone SE (2nd generation)" + destination: "platform=iOS Simulator,name=iPhone SE (3rd generation)" ) end end