Skip to content

Commit

Permalink
[store] post nil when alias is removed (#67)
Browse files Browse the repository at this point in the history
  • Loading branch information
pjechris authored Mar 27, 2024
1 parent 86cac19 commit 055a2d2
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 9 deletions.
43 changes: 36 additions & 7 deletions Sources/CohesionKit/EntityStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -349,25 +349,54 @@ extension EntityStore {
extension EntityStore {
/// Removes an alias from the storage
public func removeAlias<T>(named: AliasKey<T>) {
refAliases[named] = nil
logger?.didUnregisterAlias(named)
transaction {
if let alias = refAliases[named] {
do {
try alias.updateEntity(AliasContainer(key: named, content: nil), modifiedAt: nil)
logger?.didUnregisterAlias(named)
}
catch {

}
}
}
}

/// Removes an alias from the storage
public func removeAlias<C: Collection>(named: AliasKey<C>) {
refAliases[named] = nil
logger?.didUnregisterAlias(named)
transaction {
if let alias = refAliases[named] {
do {
try alias.updateEntity(AliasContainer(key: named, content: nil), modifiedAt: nil)
logger?.didUnregisterAlias(named)
}
catch {

}
}
}

}

/// Removes all alias from identity map
public func removeAllAlias() {
refAliases.removeAll()
transaction {
removeAliases()
}
}

/// Removes all alias AND all objects stored weakly. You should not need this method and rather use `removeAlias`.
/// But this can be useful if you fear retain cycles
public func removeAll() {
refAliases.removeAll()
storage.removeAll()
transaction {
removeAliases()
storage.removeAll()
}
}

private func removeAliases() {
for (_, node) in refAliases {
node.nullify()
}
}
}
16 changes: 15 additions & 1 deletion Sources/CohesionKit/Storage/AliasContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,18 @@ extension AliasContainer: CollectionIdentifiableKeyPathsEraser where T: MutableC
var erasedEntitiesKeyPaths: [Any] {
[PartialIdentifiableKeyPath<Self>(\.content)]
}
}
}

extension AliasContainer: Equatable where T: Equatable {

}

protocol Nullable {
func nullified() -> Self
}

extension AliasContainer: Nullable {
func nullified() -> AliasContainer<T> {
AliasContainer(key: key, content: nil)
}
}
2 changes: 1 addition & 1 deletion Sources/CohesionKit/Storage/AliasStorage.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// Keep a strong reference on each aliased node
typealias AliasStorage = [String: Any]
typealias AliasStorage = [String: AnyEntityNode]

extension AliasStorage {
subscript<T>(_ aliasKey: AliasKey<T>) -> EntityNode<AliasContainer<T>>? {
Expand Down
8 changes: 8 additions & 0 deletions Sources/CohesionKit/Storage/EntityNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import Combine
/// Typed erased protocol
protocol AnyEntityNode: AnyObject {
var value: Any { get }

func nullify()
}

/// A graph node representing a entity of type `T` and its children. Anytime one of its children is updated the node
Expand Down Expand Up @@ -53,6 +55,12 @@ class EntityNode<T>: AnyEntityNode {
onChange?(self)
}

func nullify() {
if let value = ref.value as? Nullable {
try? updateEntity(value.nullified() as! T, modifiedAt: nil)
}
}

func removeAllChildren() {
children = [:]
}
Expand Down
31 changes: 31 additions & 0 deletions Tests/CohesionKitTests/EntityStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,37 @@ extension EntityStoreTests {
}
}

// MARK: Remove
extension EntityStoreTests {
func test_removeAlias_itEnqueuesNilInRegistry() {
let registry = ObserverRegistryStub()
let store = EntityStore(registry: registry)

_ = store.store(entity: SingleNodeFixture(id: 1), named: .test)

registry.clearPendingChangesStub()

store.removeAlias(named: .test)

XCTAssertTrue(registry.hasPendingChange(for: AliasContainer(key: .test, content: nil)))
}

func test_removeAllAlias_itEnqueuesNilForEachAlias() {
let registry = ObserverRegistryStub()
let store = EntityStore(registry: registry)

_ = store.store(entity: SingleNodeFixture(id: 1), named: .test)
_ = store.store(entities: [SingleNodeFixture(id: 2)], named: .listOfNodes)

registry.clearPendingChangesStub()

store.removeAllAlias()

XCTAssertTrue(registry.hasPendingChange(for: AliasContainer(key: .test, content: nil)))
XCTAssertTrue(registry.hasPendingChange(for: AliasContainer(key: .listOfNodes, content: nil)))
}
}

private extension AliasKey where T == SingleNodeFixture {
static let test = AliasKey(named: "test")
}
Expand Down

0 comments on commit 055a2d2

Please sign in to comment.