From 1658e26c9c6d3153089810d2a232366bf0db0723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Unneb=C3=A4ck?= Date: Sat, 1 Jun 2019 10:45:47 +0100 Subject: [PATCH 1/2] Back WeakDictionary with NSMapTable --- WeakDictionary.xcodeproj/project.pbxproj | 20 --- WeakDictionary/WeakDictionary.swift | 115 ++++++++++++---- .../WeakDictionaryKeyReference.swift | 36 ----- WeakDictionary/WeakDictionaryReference.swift | 21 --- WeakDictionary/WeakKeyDictionary.swift | 128 +++++++++++++----- 5 files changed, 180 insertions(+), 140 deletions(-) delete mode 100644 WeakDictionary/WeakDictionaryKeyReference.swift delete mode 100644 WeakDictionary/WeakDictionaryReference.swift diff --git a/WeakDictionary.xcodeproj/project.pbxproj b/WeakDictionary.xcodeproj/project.pbxproj index 0238314..3e9cf0e 100644 --- a/WeakDictionary.xcodeproj/project.pbxproj +++ b/WeakDictionary.xcodeproj/project.pbxproj @@ -28,17 +28,9 @@ E1B662061DBC53C000F9C758 /* WeakDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E101D83C1DB76D1A00B5BDB8 /* WeakDictionary.swift */; }; E1B662071DBC53C600F9C758 /* WeakDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = E101D8251DB76CE800B5BDB8 /* WeakDictionary.h */; settings = {ATTRIBUTES = (Public, ); }; }; E1CF11E021DCA87200488793 /* WeakKeyDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CF11DF21DCA87200488793 /* WeakKeyDictionary.swift */; }; - E1CF11E221DCA8AC00488793 /* WeakDictionaryKeyReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CF11E121DCA8AC00488793 /* WeakDictionaryKeyReference.swift */; }; - E1CF11E421DCA90B00488793 /* WeakDictionaryReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CF11E321DCA90B00488793 /* WeakDictionaryReference.swift */; }; E1CF11E621DD783700488793 /* WeakKeyDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CF11DF21DCA87200488793 /* WeakKeyDictionary.swift */; }; E1CF11E721DD783800488793 /* WeakKeyDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CF11DF21DCA87200488793 /* WeakKeyDictionary.swift */; }; E1CF11E821DD783900488793 /* WeakKeyDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CF11DF21DCA87200488793 /* WeakKeyDictionary.swift */; }; - E1CF11E921DD783C00488793 /* WeakDictionaryKeyReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CF11E121DCA8AC00488793 /* WeakDictionaryKeyReference.swift */; }; - E1CF11EA21DD783D00488793 /* WeakDictionaryKeyReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CF11E121DCA8AC00488793 /* WeakDictionaryKeyReference.swift */; }; - E1CF11EB21DD783D00488793 /* WeakDictionaryKeyReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CF11E121DCA8AC00488793 /* WeakDictionaryKeyReference.swift */; }; - E1CF11EC21DD784100488793 /* WeakDictionaryReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CF11E321DCA90B00488793 /* WeakDictionaryReference.swift */; }; - E1CF11ED21DD784200488793 /* WeakDictionaryReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CF11E321DCA90B00488793 /* WeakDictionaryReference.swift */; }; - E1CF11EE21DD784300488793 /* WeakDictionaryReference.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CF11E321DCA90B00488793 /* WeakDictionaryReference.swift */; }; E1CF11F021DDAE9F00488793 /* DocumentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CF11EF21DDAE9F00488793 /* DocumentationTests.swift */; }; E1CF11F121DDAE9F00488793 /* DocumentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CF11EF21DDAE9F00488793 /* DocumentationTests.swift */; }; E1CF11F221DDAE9F00488793 /* DocumentationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1CF11EF21DDAE9F00488793 /* DocumentationTests.swift */; }; @@ -84,8 +76,6 @@ E1B661E61DBC509800F9C758 /* WeakDictionary-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "WeakDictionary-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; E1B661FE1DBC530E00F9C758 /* WeakDictionary.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = WeakDictionary.framework; sourceTree = BUILT_PRODUCTS_DIR; }; E1CF11DF21DCA87200488793 /* WeakKeyDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakKeyDictionary.swift; sourceTree = ""; }; - E1CF11E121DCA8AC00488793 /* WeakDictionaryKeyReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakDictionaryKeyReference.swift; sourceTree = ""; }; - E1CF11E321DCA90B00488793 /* WeakDictionaryReference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakDictionaryReference.swift; sourceTree = ""; }; E1CF11EF21DDAE9F00488793 /* DocumentationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentationTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -175,8 +165,6 @@ E101D8261DB76CE800B5BDB8 /* Info.plist */, E101D83C1DB76D1A00B5BDB8 /* WeakDictionary.swift */, E1CF11DF21DCA87200488793 /* WeakKeyDictionary.swift */, - E1CF11E121DCA8AC00488793 /* WeakDictionaryKeyReference.swift */, - E1CF11E321DCA90B00488793 /* WeakDictionaryReference.swift */, ); path = WeakDictionary; sourceTree = ""; @@ -509,9 +497,7 @@ buildActionMask = 2147483647; files = ( E101D83D1DB76D1A00B5BDB8 /* WeakDictionary.swift in Sources */, - E1CF11E421DCA90B00488793 /* WeakDictionaryReference.swift in Sources */, E1CF11E021DCA87200488793 /* WeakKeyDictionary.swift in Sources */, - E1CF11E221DCA8AC00488793 /* WeakDictionaryKeyReference.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -531,9 +517,7 @@ buildActionMask = 2147483647; files = ( E1B661D81DBC501300F9C758 /* WeakDictionary.swift in Sources */, - E1CF11EE21DD784300488793 /* WeakDictionaryReference.swift in Sources */, E1CF11E621DD783700488793 /* WeakKeyDictionary.swift in Sources */, - E1CF11E921DD783C00488793 /* WeakDictionaryKeyReference.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -553,9 +537,7 @@ buildActionMask = 2147483647; files = ( E1B661F51DBC50FD00F9C758 /* WeakDictionary.swift in Sources */, - E1CF11ED21DD784200488793 /* WeakDictionaryReference.swift in Sources */, E1CF11E721DD783800488793 /* WeakKeyDictionary.swift in Sources */, - E1CF11EA21DD783D00488793 /* WeakDictionaryKeyReference.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -575,9 +557,7 @@ buildActionMask = 2147483647; files = ( E1B662061DBC53C000F9C758 /* WeakDictionary.swift in Sources */, - E1CF11EC21DD784100488793 /* WeakDictionaryReference.swift in Sources */, E1CF11E821DD783900488793 /* WeakKeyDictionary.swift in Sources */, - E1CF11EB21DD783D00488793 /* WeakDictionaryKeyReference.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/WeakDictionary/WeakDictionary.swift b/WeakDictionary/WeakDictionary.swift index d6cd8ef..1ef19f7 100644 --- a/WeakDictionary/WeakDictionary.swift +++ b/WeakDictionary/WeakDictionary.swift @@ -8,28 +8,41 @@ import Foundation +fileprivate class KeyBox: NSObject { + let key: T + + init(_ key: T) { + self.key = key + super.init() + } + + @objc override var hash: Int { + return key.hashValue + } + + @objc override func isEqual(_ object: Any?) -> Bool { + return key == (object as! KeyBox).key + } +} + public struct WeakDictionary { - private var storage: [Key: WeakDictionaryReference] + private var storage: NSMapTable, Value> public init() { - self.init(storage: [Key: WeakDictionaryReference]()) + self.init(storage: NSMapTable(keyOptions: .strongMemory, valueOptions: .weakMemory)) } public init(dictionary: [Key: Value]) { - var newStorage = [Key: WeakDictionaryReference]() - dictionary.forEach({ key, value in newStorage[key] = WeakDictionaryReference(value: value) }) + let newStorage = NSMapTable, Value>(keyOptions: .strongMemory, valueOptions: .weakMemory, capacity: dictionary.capacity) + dictionary.forEach({ key, value in newStorage.setObject(value, forKey: KeyBox(key)) }) self.init(storage: newStorage) } - private init(storage: [Key: WeakDictionaryReference]) { + private init(storage: NSMapTable, Value>) { self.storage = storage } - public mutating func reap() { - storage = weakDictionary().storage - } - public func weakDictionary() -> WeakDictionary { return self[startIndex ..< endIndex] } @@ -37,9 +50,9 @@ public struct WeakDictionary { public func dictionary() -> [Key: Value] { var newStorage = [Key: Value]() - storage.forEach { key, value in - if let retainedValue = value.value { - newStorage[key] = retainedValue + for key in storage.keyEnumerator() { + if let value = storage.object(forKey: (key as! KeyBox)) { + newStorage[(key as! KeyBox).key] = value } } @@ -47,51 +60,95 @@ public struct WeakDictionary { } } +public class WeakDictionaryIndex { + fileprivate let key: KeyBox? + + private let position: UInt + private var enumerator: NSEnumerator? + + fileprivate lazy var next: WeakDictionaryIndex = { + defer { self.enumerator = nil } + return .init(enumerator: enumerator, position: position + 1) + }() + + fileprivate convenience init(enumerator: NSEnumerator?, position: UInt = 0) { + if let key = enumerator?.nextObject() as! KeyBox? { + self.init(key: key, enumerator: enumerator, position: position) + } else { + self.init(key: nil, enumerator: nil, position: .max) + } + } + + fileprivate convenience init() { + self.init(key: nil, enumerator: nil, position: .max) + } + + private init(key: KeyBox?, enumerator: NSEnumerator?, position: UInt) { + self.key = key + self.enumerator = enumerator + self.position = position + } +} + +extension WeakDictionaryIndex: Equatable { + public static func == (lhs: WeakDictionaryIndex, rhs: WeakDictionaryIndex) -> Bool { + return lhs.position == rhs.position + } +} + +extension WeakDictionaryIndex: Comparable { + public static func < (lhs: WeakDictionaryIndex, rhs: WeakDictionaryIndex) -> Bool { + return lhs.position < rhs.position + } +} + extension WeakDictionary: Collection { - public typealias Index = DictionaryIndex> + public typealias Index = WeakDictionaryIndex public var startIndex: Index { - return storage.startIndex + return Index(enumerator: storage.keyEnumerator()) } public var endIndex: Index { - return storage.endIndex + return Index() } public func index(after index: Index) -> Index { - return storage.index(after: index) + return index.next } - public subscript(position: Index) -> (Key, WeakDictionaryReference) { - return storage[position] + public subscript(position: Index) -> (Key, Value) { + guard let key = position.key else { + fatalError("Attempting to access WeakDictionary elements using an invalid index") + } + + return (key.key, storage.object(forKey: key)!) } public subscript(key: Key) -> Value? { get { - guard let valueRef = storage[key] else { - return nil - } - - return valueRef.value + return storage.object(forKey: KeyBox(key)) } set { guard let value = newValue else { - storage[key] = nil + storage.removeObject(forKey: KeyBox(key)) return } - storage[key] = WeakDictionaryReference(value: value) + storage.setObject(value, forKey: KeyBox(key)) } } public subscript(bounds: Range) -> WeakDictionary { - let subStorage = storage[bounds.lowerBound ..< bounds.upperBound] - var newStorage = [Key: WeakDictionaryReference]() + let newStorage = NSMapTable, Value>(keyOptions: .strongMemory, valueOptions: .weakMemory) - subStorage.filter { _, value in return value.value != nil } - .forEach { key, value in newStorage[key] = value } + var pos = bounds.lowerBound + while pos.key != nil && pos != bounds.upperBound { + newStorage.setObject(storage.object(forKey: pos.key), forKey: pos.key) + pos = pos.next + } return WeakDictionary(storage: newStorage) } diff --git a/WeakDictionary/WeakDictionaryKeyReference.swift b/WeakDictionary/WeakDictionaryKeyReference.swift deleted file mode 100644 index 6d74e6a..0000000 --- a/WeakDictionary/WeakDictionaryKeyReference.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// WeakDictionaryKeyReference.swift -// WeakDictionary-iOS -// -// Created by Nicholas Cross on 2/1/19. -// Copyright © 2019 Nicholas Cross. All rights reserved. -// - -import Foundation - -public struct WeakDictionaryKey : Hashable { - - private weak var baseKey: Key? - private let hash: Int - private var retainedValue: Value? - private let nilKeyHash = UUID().hashValue - - public init(key: Key, value: Value? = nil) { - baseKey = key - retainedValue = value - hash = key.hashValue - } - - public static func == (lhs: WeakDictionaryKey, rhs: WeakDictionaryKey) -> Bool { - return (lhs.baseKey != nil && rhs.baseKey != nil && lhs.baseKey == rhs.baseKey) - || lhs.hashValue == rhs.hashValue - } - - public var hashValue: Int { - return baseKey != nil ? hash : nilKeyHash - } - - public var key: Key? { - return baseKey - } -} diff --git a/WeakDictionary/WeakDictionaryReference.swift b/WeakDictionary/WeakDictionaryReference.swift deleted file mode 100644 index c1789da..0000000 --- a/WeakDictionary/WeakDictionaryReference.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// WeakDictionaryReference.swift -// WeakDictionary-iOS -// -// Created by Nicholas Cross on 2/1/19. -// Copyright © 2019 Nicholas Cross. All rights reserved. -// - -import Foundation - -public struct WeakDictionaryReference { - private weak var referencedValue: Value? - - init(value: Value) { - referencedValue = value - } - - public var value: Value? { - return referencedValue - } -} diff --git a/WeakDictionary/WeakKeyDictionary.swift b/WeakDictionary/WeakKeyDictionary.swift index 7d804c1..9956767 100644 --- a/WeakDictionary/WeakKeyDictionary.swift +++ b/WeakDictionary/WeakKeyDictionary.swift @@ -7,46 +7,55 @@ // import Foundation +import ObjectiveC + +fileprivate var AssociatedObjectHandle: UInt8 = 0 + +fileprivate class WeakKeyBox: NSObject { + weak var key: T! + + init(_ key: T) { + self.key = key + super.init() + objc_setAssociatedObject(key, &AssociatedObjectHandle, self, .OBJC_ASSOCIATION_RETAIN) + } + + @objc override var hash: Int { + return key.hashValue + } + + @objc override func isEqual(_ object: Any?) -> Bool { + return key == (object as! WeakKeyBox).key + } +} public struct WeakKeyDictionary { - private var storage: WeakDictionary, Value> + private var storage: NSMapTable, Value> private let valuesRetainedByKey: Bool public init(valuesRetainedByKey: Bool = false) { self.init( - storage: WeakDictionary, Value>(), + storage: NSMapTable(keyOptions: .weakMemory, valueOptions: valuesRetainedByKey ? .strongMemory : .weakMemory), valuesRetainedByKey: valuesRetainedByKey ) } public init(dictionary: [Key: Value], valuesRetainedByKey: Bool = false) { - var newStorage = WeakDictionary, Value>() + let newStorage = NSMapTable, Value>(keyOptions: .weakMemory, valueOptions: valuesRetainedByKey ? .strongMemory : .weakMemory, capacity: dictionary.capacity) dictionary.forEach { key, value in - var keyRef: WeakDictionaryKey! - - if valuesRetainedByKey { - keyRef = WeakDictionaryKey(key: key, value: value) - } else { - keyRef = WeakDictionaryKey(key: key) - } - - newStorage[keyRef] = value + newStorage.setObject(value, forKey: WeakKeyBox(key)) } self.init(storage: newStorage, valuesRetainedByKey: valuesRetainedByKey) } - private init(storage: WeakDictionary, Value>, valuesRetainedByKey: Bool = false) { + private init(storage: NSMapTable, Value>, valuesRetainedByKey: Bool = false) { self.storage = storage self.valuesRetainedByKey = valuesRetainedByKey } - public mutating func reap() { - storage = weakKeyDictionary().storage - } - public func weakDictionary() -> WeakDictionary { return dictionary().weakDictionary() } @@ -58,9 +67,9 @@ public struct WeakKeyDictionary { public func dictionary() -> [Key: Value] { var newStorage = [Key: Value]() - storage.forEach { key, value in - if let retainedKey = key.key, let retainedValue = value.value { - newStorage[retainedKey] = retainedValue + for key in storage.keyEnumerator() { + if let value = storage.object(forKey: (key as! WeakKeyBox)) { + newStorage[(key as! WeakKeyBox).key] = value } } @@ -68,44 +77,95 @@ public struct WeakKeyDictionary { } } +public class WaekKeyDictionaryIndex { + fileprivate let key: WeakKeyBox? + + private let position: UInt + private var enumerator: NSEnumerator? + + fileprivate lazy var next: WaekKeyDictionaryIndex = { + defer { self.enumerator = nil } + return .init(enumerator: enumerator, position: position + 1) + }() + + fileprivate convenience init(enumerator: NSEnumerator?, position: UInt = 0) { + if let key = enumerator?.nextObject() as! WeakKeyBox? { + self.init(key: key, enumerator: enumerator, position: position) + } else { + self.init(key: nil, enumerator: nil, position: .max) + } + } + + fileprivate convenience init() { + self.init(key: nil, enumerator: nil, position: .max) + } + + private init(key: WeakKeyBox?, enumerator: NSEnumerator?, position: UInt) { + self.key = key + self.enumerator = enumerator + self.position = position + } +} + +extension WaekKeyDictionaryIndex: Equatable { + public static func == (lhs: WaekKeyDictionaryIndex, rhs: WaekKeyDictionaryIndex) -> Bool { + return lhs.position == rhs.position + } +} + +extension WaekKeyDictionaryIndex: Comparable { + public static func < (lhs: WaekKeyDictionaryIndex, rhs: WaekKeyDictionaryIndex) -> Bool { + return lhs.position < rhs.position + } +} + extension WeakKeyDictionary: Collection { - public typealias Index = DictionaryIndex, WeakDictionaryReference> + public typealias Index = WaekKeyDictionaryIndex public var startIndex: Index { - return storage.startIndex + return Index(enumerator: storage.keyEnumerator()) } public var endIndex: Index { - return storage.endIndex + return Index() } public func index(after index: Index) -> Index { - return storage.index(after: index) + return index.next } - public subscript(position: Index) -> (WeakDictionaryKey, WeakDictionaryReference) { - return storage[position] + public subscript(position: Index) -> (Key, Value) { + guard let key = position.key else { + fatalError("Attempting to access WeakKeyDictionary elements using an invalid index") + } + + return (key.key, storage.object(forKey: key)!) } public subscript(key: Key) -> Value? { get { - return storage[WeakDictionaryKey(key: key)] + return storage.object(forKey: WeakKeyBox(key)) } set { - let retainedValue = valuesRetainedByKey ? newValue : nil - let weakKey = WeakDictionaryKey(key: key, value: retainedValue) - storage[weakKey] = newValue + guard let value = newValue else { + storage.removeObject(forKey: WeakKeyBox(key)) + return + } + + storage.setObject(value, forKey: WeakKeyBox(key)) } } public subscript(bounds: Range) -> WeakKeyDictionary { - let subStorage = storage[bounds.lowerBound ..< bounds.upperBound] - var newStorage = WeakDictionary, Value>() + let newStorage = NSMapTable, Value>(keyOptions: .strongMemory, valueOptions: .weakMemory) - subStorage.filter { key, value in return key.key != nil && value.value != nil } - .forEach { key, value in newStorage[key] = value.value } + var pos = bounds.lowerBound + while pos.key != nil && pos != bounds.upperBound { + newStorage.setObject(storage.object(forKey: pos.key), forKey: pos.key) + pos = pos.next + } return WeakKeyDictionary(storage: newStorage) } From e88e3fd421c3719e011db18ddf8288c6c100bd2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Unneb=C3=A4ck?= Date: Sun, 2 Jun 2019 13:23:35 +0100 Subject: [PATCH 2/2] WIP: Update tests for NSMapTable storage backend --- WeakDictionaryTests/WeakDictionaryTests.swift | 122 ++++++++------- .../WeakKeyDictionaryTests.swift | 139 ++++++++++-------- .../WeakKeyRetainsValueTests.swift | 39 ++--- 3 files changed, 165 insertions(+), 135 deletions(-) diff --git a/WeakDictionaryTests/WeakDictionaryTests.swift b/WeakDictionaryTests/WeakDictionaryTests.swift index 7ab20ad..5a1ddb2 100644 --- a/WeakDictionaryTests/WeakDictionaryTests.swift +++ b/WeakDictionaryTests/WeakDictionaryTests.swift @@ -25,91 +25,103 @@ class WeakDictionaryTests: XCTestCase { func testAssignment() { let retainedKey = "avalue" - var transientValue: ExampleValue? = ExampleValue() - weakDictionary[retainedKey] = transientValue - XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding a reference") +XCTAssertEqual(weakDictionary.count, 0, "Expected to be left holding no references") + autoreleasepool { + let transientValue: ExampleValue? = ExampleValue() - XCTAssertNotNil(weakDictionary[retainedKey], "Expected avalue to have a value") + weakDictionary[retainedKey] = transientValue + XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding a reference") - transientValue = nil - XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding an empty reference") + XCTAssertNotNil(weakDictionary[retainedKey], "Expected avalue to have a value") + } + XCTAssertEqual(weakDictionary.count, 0, "Expected to be left holding no references") XCTAssertNil(weakDictionary[retainedKey], "Expected avalue to have no value") weakDictionary[retainedKey] = nil XCTAssertEqual(weakDictionary.count, 0, "Expected to be left holding no references") } - func testReaping() { - var transientValue: ExampleValue? = ExampleValue() - weakDictionary["avalue"] = transientValue - XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding an empty reference") - - var reaped = weakDictionary.weakDictionary() - XCTAssertEqual(reaped.count, 1, "Expected to be left holding a single reference") - - transientValue = nil - reaped = weakDictionary.weakDictionary() - XCTAssertEqual(reaped.count, 0, "Expected to be left holding no references") - } +// func testReaping() { +// var transientValue: ExampleValue? = ExampleValue() +// weakDictionary["avalue"] = transientValue +// XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding an empty reference") +// +// var reaped = weakDictionary.weakDictionary() +// XCTAssertEqual(reaped.count, 1, "Expected to be left holding a single reference") +// +// transientValue = nil +// reaped = weakDictionary.weakDictionary() +// XCTAssertEqual(reaped.count, 0, "Expected to be left holding no references") +// } +// +// func testMutatingReap() { +// var transientValue: ExampleValue? = ExampleValue() +// weakDictionary["avalue"] = transientValue +// XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding a single reference") +// +// weakDictionary.reap() +// XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding a single reference") +// +// transientValue = nil +// weakDictionary.reap() +// XCTAssertEqual(weakDictionary.count, 0, "Expected to be left holding no references") +// } - func testMutatingReap() { + func testStrongification() { var transientValue: ExampleValue? = ExampleValue() - weakDictionary["avalue"] = transientValue - XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding a single reference") + var reaped: WeakDictionary! - weakDictionary.reap() - XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding a single reference") + autoreleasepool { + weakDictionary["avalue"] = transientValue + XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding an empty reference") - transientValue = nil - weakDictionary.reap() - XCTAssertEqual(weakDictionary.count, 0, "Expected to be left holding no references") - } + reaped = weakDictionary.weakDictionary() + XCTAssertEqual(reaped.count, 1, "Expected to be left holding a single reference") - func testStrongification() { - var transientValue: ExampleValue? = ExampleValue() - weakDictionary["avalue"] = transientValue - XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding an empty reference") + var strongDictionary: [String: ExampleValue]? = weakDictionary.dictionary() + XCTAssert(strongDictionary?.count == 1, "Expected to be holding a single key value pair") - var reaped = weakDictionary.weakDictionary() - XCTAssertEqual(reaped.count, 1, "Expected to be left holding a single reference") + transientValue = nil - var strongDictionary: [String: ExampleValue]? = weakDictionary.dictionary() - XCTAssert(strongDictionary?.count == 1, "Expected to be holding a single key value pair") + reaped = weakDictionary.weakDictionary() + XCTAssertEqual(reaped.count, 1, "Expected to be left holding a single reference \(reaped.count)") - transientValue = nil - reaped = weakDictionary.weakDictionary() - XCTAssertEqual(reaped.count, 1, "Expected to be left holding a single reference \(reaped.count)") - - weak var weakSock: ExampleValue? = strongDictionary?["avalue"] - XCTAssertNotNil(weakSock, "Expected to find sock in strong dictionary") + weak var weakSock: ExampleValue? = strongDictionary?["avalue"] + XCTAssertNotNil(weakSock, "Expected to find sock in strong dictionary") + } - strongDictionary = nil reaped = weakDictionary.weakDictionary() XCTAssertEqual(reaped.count, 0, "Expected unreferenced values to be released") transientValue = ExampleValue() weakDictionary["avalue"] = transientValue transientValue = nil - XCTAssertEqual(weakDictionary.count, 1, "Expected to be holding an empty value reference") - XCTAssertEqual(weakDictionary.dictionary().count, 0, "Expected empty references to be ignored") + XCTAssertEqual(weakDictionary.count, 0, "Expected dictionary to be empty") + XCTAssertEqual(weakDictionary.dictionary().count, 0, "Expected dictionary to be empty") } func testInitWithDictionary() { - var dictionary: [String: ExampleValue]? = [ - "Left": ExampleValue(), - "Right": ExampleValue() - ] + autoreleasepool { + var accessValue: ExampleValue! + + autoreleasepool { + let dictionary = [ + "Left": ExampleValue(), + "Right": ExampleValue() + ] + + weakDictionary = WeakDictionary(dictionary: dictionary) + XCTAssertEqual(weakDictionary.count, 2, "Expected dictionary to be initialised with two references") - weakDictionary = WeakDictionary(dictionary: dictionary!) - XCTAssertEqual(weakDictionary.count, 2, "Expected dictionary to be initialised with two references") + accessValue = weakDictionary["Left"] + XCTAssertNotNil(accessValue, "Expected value to be available for key") + } - let accessValue = weakDictionary["Left"] - XCTAssertNotNil(accessValue, "Expected value to be available for key") + XCTAssertEqual(weakDictionary.count, 1, "Expected dictionary to be empty") + } - dictionary = nil - weakDictionary.reap() - XCTAssertEqual(weakDictionary.count, 1, "Expected nullified weak references to be reaped") + XCTAssertEqual(weakDictionary.count, 0, "Expected dictionary to be empty") } func testConversionFromDictionaryToWeakDictionary() { diff --git a/WeakDictionaryTests/WeakKeyDictionaryTests.swift b/WeakDictionaryTests/WeakKeyDictionaryTests.swift index aff50ce..120246b 100644 --- a/WeakDictionaryTests/WeakKeyDictionaryTests.swift +++ b/WeakDictionaryTests/WeakKeyDictionaryTests.swift @@ -22,13 +22,16 @@ class WeakKeyDictionaryTests: XCTestCase { func testAssignment() { var referencingKey: ExampleKey? = ExampleKey(name: "Left") var referencedValue: ExampleValue? = ExampleValue() - weakDictionary[referencingKey!] = referencedValue - XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding a reference") - XCTAssertNotNil(weakDictionary[referencingKey!], "Expected key to have a value") + autoreleasepool { + weakDictionary[referencingKey!] = referencedValue + XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding a reference") - referencedValue = nil - XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding an empty reference") + XCTAssertNotNil(weakDictionary[referencingKey!], "Expected key to have a value") + referencedValue = nil + } + + XCTAssertEqual(weakDictionary.count, 0, "Expected to be left holding an empty reference") weak var accessValue = weakDictionary[ExampleKey(name: "Left")] XCTAssertNil(accessValue, "Expected key to have no value") @@ -36,25 +39,27 @@ class WeakKeyDictionaryTests: XCTestCase { weakDictionary[ExampleKey(name: "Left")] = nil XCTAssertEqual(weakDictionary.count, 0, "Expected to be left holding no references") - referencingKey = ExampleKey(name: "Right") - referencedValue = ExampleValue() - weakDictionary[referencingKey!] = referencedValue - XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding a reference") + autoreleasepool { + referencingKey = ExampleKey(name: "Right") + referencedValue = ExampleValue() + weakDictionary[referencingKey!] = referencedValue + XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding a reference") - accessValue = weakDictionary[ExampleKey(name: "Right")] - XCTAssertNotNil(accessValue, "Expected key to have a accessible value") + accessValue = weakDictionary[ExampleKey(name: "Right")] + XCTAssertNotNil(accessValue, "Expected key to have a accessible value") - referencingKey = nil - XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding a nil reference") + referencingKey = nil + } + XCTAssertEqual(weakDictionary.count, 0, "Expected to be left holding no references") accessValue = weakDictionary[ExampleKey(name: "Right")] XCTAssertNil(accessValue, "Expected key to have no accessible value") weakDictionary[ExampleKey(name: "Right")] = nil - XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding a nil reference") + XCTAssertEqual(weakDictionary.count, 0, "Expected to be left holding no references") weakDictionary[ExampleKey(name: "Fleeting")] = ExampleValue() - XCTAssertEqual(weakDictionary.count, 2, "Expected to be left holding another nil reference") + XCTAssertEqual(weakDictionary.count, 0, "Expected to be left holding no references") } func testKeyReaping() { @@ -80,30 +85,27 @@ class WeakKeyDictionaryTests: XCTestCase { } func testValueReaping() { - let retainedKey: ExampleKey = ExampleKey(name: "Left") - var transientValue: ExampleValue? = ExampleValue() - weakDictionary[retainedKey] = transientValue - XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding an empty reference") - - var reaped = weakDictionary.weakKeyDictionary() - XCTAssertEqual(reaped.count, 1, "Expected to be left holding a single reference") + autoreleasepool { + let retainedKey: ExampleKey = ExampleKey(name: "Left") + let transientValue: ExampleValue? = ExampleValue() + weakDictionary[retainedKey] = transientValue + XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding an empty reference") + + let reaped = weakDictionary.weakKeyDictionary() + XCTAssertEqual(reaped.count, 1, "Expected to be left holding a single reference") + } - transientValue = nil - reaped = weakDictionary.weakKeyDictionary() + let reaped = weakDictionary.weakKeyDictionary() XCTAssertEqual(reaped.count, 0, "Expected to be left holding no references") } func testMutatingReap() { - var transientKey: ExampleKey? = ExampleKey(name: "Left") - let retainedValue: ExampleValue = ExampleValue() - weakDictionary[transientKey!] = retainedValue - XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding an empty reference") - - weakDictionary.reap() - XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding a single reference") - - transientKey = nil - weakDictionary.reap() + autoreleasepool { + let transientKey: ExampleKey? = ExampleKey(name: "Left") + let retainedValue: ExampleValue = ExampleValue() + weakDictionary[transientKey!] = retainedValue + XCTAssertEqual(weakDictionary.count, 1, "Expected to be left holding a single reference") + } XCTAssertEqual(weakDictionary.count, 0, "Expected nil references to be reaped") weakDictionary[ExampleKey(name: "Fleeting")] = ExampleValue() @@ -111,56 +113,69 @@ class WeakKeyDictionaryTests: XCTestCase { weakDictionary[ExampleKey(name: "Fleeting2")] = ExampleValue() weakDictionary[ExampleKey(name: "Fleeting3")] = ExampleValue() weakDictionary[ExampleKey(name: "Fleeting4")] = ExampleValue() - XCTAssertEqual(weakDictionary.count, 5, "Expected to be left holding nil references") - weakDictionary.reap() XCTAssertEqual(weakDictionary.count, 0, "Expected nil references to be reaped") } func testStrongification() { let retainedKey: ExampleKey = ExampleKey(name: "Left") var transientValue: ExampleValue? = ExampleValue() - weakDictionary[retainedKey] = transientValue - XCTAssert(weakDictionary.count == 1, "Expected to be left holding an empty reference \(weakDictionary.count)") - var reaped = weakDictionary.weakKeyDictionary() - XCTAssertEqual(reaped.count, 1, "Expected to be left holding a single reference") + var reaped: WeakKeyDictionary! + var strongDictionary: [ExampleKey: ExampleValue]! - var strongDictionary: [ExampleKey: ExampleValue]? = weakDictionary.dictionary() - XCTAssert(strongDictionary?.count == 1, "Expected to be holding a single key value pair") + autoreleasepool { + weakDictionary[retainedKey] = transientValue + XCTAssert(weakDictionary.count == 1, "Expected to be left holding an empty reference \(weakDictionary.count)") - transientValue = nil - reaped = weakDictionary.weakKeyDictionary() - XCTAssertEqual(reaped.count, 1, "Expected to be left holding a single reference") + reaped = weakDictionary.weakKeyDictionary() + XCTAssertEqual(reaped.count, 1, "Expected to be left holding a single reference") + } - weak var weakExample: ExampleValue? = strongDictionary?[retainedKey] - XCTAssertNotNil(weakExample, "Expected to find Example in strong dictionary") + autoreleasepool { + strongDictionary = weakDictionary.dictionary() + XCTAssert(strongDictionary?.count == 1, "Expected to be holding a single key value pair") + + transientValue = nil + } + + autoreleasepool { + reaped = weakDictionary.weakKeyDictionary() + XCTAssertEqual(reaped.count, 1, "Expected to be left holding a single reference") + + weak var weakExample: ExampleValue? = strongDictionary?[retainedKey] + XCTAssertNotNil(weakExample, "Expected to find Example in strong dictionary") + } - strongDictionary = nil reaped = weakDictionary.weakKeyDictionary() XCTAssertEqual(reaped.count, 0, "Expected unreferenced values to be released") - transientValue = ExampleValue() - weakDictionary[retainedKey] = transientValue - transientValue = nil - XCTAssertEqual(weakDictionary.count, 1, "Expected to be holding an empty value reference") + autoreleasepool { + transientValue = ExampleValue() + weakDictionary[retainedKey] = transientValue + transientValue = nil + } + XCTAssertEqual(weakDictionary.count, 0, "Expected to be holding no references") XCTAssertEqual(weakDictionary.dictionary().count, 0, "Expected empty references to be ignored") } func testInitWithDictionary() { let retainedKey = ExampleKey(name: "Left") - var strongDict: [ExampleKey: ExampleValue]? = [ - retainedKey: ExampleValue(), - ExampleKey(name: "Right"): ExampleValue() - ] + var accessValue: ExampleValue! - weakDictionary = WeakKeyDictionary(dictionary: strongDict!) - XCTAssert(weakDictionary.count == 2, "Expected dictionary to be initialised with two references") + autoreleasepool { + let strongDict = [ + retainedKey: ExampleValue(), + ExampleKey(name: "Right"): ExampleValue() + ] - let accessValue = weakDictionary[retainedKey] - XCTAssertNotNil(accessValue, "Expected value to be available for key") + weakDictionary = WeakKeyDictionary(dictionary: strongDict) + XCTAssert(weakDictionary.count == 2, "Expected dictionary to be initialised with two references") + + accessValue = weakDictionary[retainedKey] + XCTAssertNotNil(accessValue, "Expected value to be available for key") + } - strongDict = nil - weakDictionary.reap() + XCTAssertNotNil(weakDictionary[retainedKey]) XCTAssertEqual(weakDictionary.count, 1, "Expected nullified weak references to be reaped") } diff --git a/WeakDictionaryTests/WeakKeyRetainsValueTests.swift b/WeakDictionaryTests/WeakKeyRetainsValueTests.swift index 0eba76d..65fe476 100644 --- a/WeakDictionaryTests/WeakKeyRetainsValueTests.swift +++ b/WeakDictionaryTests/WeakKeyRetainsValueTests.swift @@ -33,7 +33,7 @@ class WeakKeyRetainsValuesTests: XCTestCase { weak var transientAccessValue = weakDictionary[accessingKey] XCTAssertNotNil(transientAccessValue, "Expected key to have a value because it is retained by the key") - weakDictionary.reap() +// weakDictionary.reap() XCTAssertNotNil(transientAccessValue, "Expected value to exist because it is retained by the key reference") retainingKey = nil @@ -42,32 +42,35 @@ class WeakKeyRetainsValuesTests: XCTestCase { XCTAssertNil(absentAccessValue, "Expected key to no longer have a value because the key no longer retains it") XCTAssertNotNil(transientAccessValue, "Expected value to exist because it is retained by the key reference") - weakDictionary.reap() +// weakDictionary.reap() XCTAssertNil(transientAccessValue, "Expected value to be nil as it is no longer retained by the key reference") } func testInitFromDictionary() { - var transientKey: ExampleKey! = ExampleKey(name: "Left") - var transientValue: ExampleValue! = ExampleValue() - var dictionary: [ExampleKey: ExampleValue]! = [ - transientKey: transientValue, - ExampleKey(name: "Right"): ExampleValue() - ] + weak var weaklyHeldValue: ExampleValue? - weakDictionary = WeakKeyDictionary(dictionary: dictionary, valuesRetainedByKey: true) + autoreleasepool { + var transientKey: ExampleKey! = ExampleKey(name: "Left") + var transientValue: ExampleValue! = ExampleValue() + var dictionary: [ExampleKey: ExampleValue]! = [ + transientKey: transientValue, + ExampleKey(name: "Right"): ExampleValue() + ] - dictionary = nil - weak var weaklyHeldValue: ExampleValue? = transientValue - transientValue = nil + weakDictionary = WeakKeyDictionary(dictionary: dictionary, valuesRetainedByKey: true) - XCTAssertNotNil(weakDictionary[ExampleKey(name: "Left")], "Expected value to be retained by key") - XCTAssertNotNil(weaklyHeldValue, "Expected to be retained by key") + dictionary = nil + weaklyHeldValue = transientValue + transientValue = nil - transientKey = nil - XCTAssertNil(weakDictionary[ExampleKey(name: "Left")], "Expected value to no longer be accessible by key") - XCTAssertNotNil(weaklyHeldValue, "Expected to be retained by key container even though key is gone") + XCTAssertNotNil(weakDictionary[ExampleKey(name: "Left")], "Expected value to be retained by key") + XCTAssertNotNil(weaklyHeldValue, "Expected to be retained by key") + + transientKey = nil + XCTAssertNil(weakDictionary[ExampleKey(name: "Left")], "Expected value to no longer be accessible by key") + XCTAssertNotNil(weaklyHeldValue, "Expected to be retained by key container even though key is gone") + } - weakDictionary.reap() XCTAssertNil(weaklyHeldValue, "Expected weakly held reference would be released after reaping") }