Skip to content

Instantly share code, notes, and snippets.

@Patrick-Beninga-Grammarly
Created March 3, 2022 00:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Patrick-Beninga-Grammarly/dc2acb0e89b1f01b5e81e3ab226bc91b to your computer and use it in GitHub Desktop.
Save Patrick-Beninga-Grammarly/dc2acb0e89b1f01b5e81e3ab226bc91b to your computer and use it in GitHub Desktop.
import Foundation
private enum AssociatedKeys {
static var DeinitializationObserver = "DeinitializationObserver"
}
public class DependencyBox<T: AnyObject> {
public weak var object: T? {
didSet {
guard object !== oldValue else { return }
if let oldVal = oldValue {
unregisterDeinitObserver(for: oldVal)
}
if let object = object {
registerDeinitObserver(for: object)
}
listenerTokens.allObjects.forEach { $0.onChange(object) }
}
}
private var listenerTokens = NSHashTable<DependencyBoxSubscriptionToken>.weakObjects()
public init(_ object: T? = nil) {
self.object = object
if let object = object {
registerDeinitObserver(for: object)
}
}
public func subscribe(onChange: @escaping (T?) -> Void) -> DependencyBoxSubscriptionToken {
let token = DependencyBoxSubscriptionToken(self, onChange: onChange)
listenerTokens.add(token)
return token
}
private func unsubscribe(_ token: DependencyBoxSubscriptionToken) {
listenerTokens.remove(token)
}
private func registerDeinitObserver(for object: AnyObject) {
getDeinitObserver(for: object, create: true)?.boxes.add(self)
}
private func unregisterDeinitObserver(for object: AnyObject) {
getDeinitObserver(for: object, create: false)?.boxes.remove(self)
}
private func getDeinitObserver(for object: AnyObject, create: Bool) -> DeinitializationObserver? {
if let observer = objc_getAssociatedObject(object, &AssociatedKeys.DeinitializationObserver) as? DeinitializationObserver {
return observer
} else if create {
let observer = DeinitializationObserver()
objc_setAssociatedObject(
object,
&AssociatedKeys.DeinitializationObserver,
observer,
objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
return observer
} else {
return nil
}
}
private func onObjectDeinit() {
listenerTokens.allObjects.forEach { $0.onChange(object) }
}
private class DeinitializationObserver {
let boxes = NSHashTable<DependencyBox<T>>.weakObjects()
deinit {
boxes.allObjects.forEach { $0.onObjectDeinit() }
}
}
public final class DependencyBoxSubscriptionToken {
private weak var box: DependencyBox?
fileprivate let onChange: (T?) -> Void
init(_ box: DependencyBox, onChange: @escaping (T?) -> Void) {
self.box = box
self.onChange = onChange
}
public func invalidate() {
box?.unsubscribe(self)
}
deinit {
self.box?.unsubscribe(self)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment