diff --git a/Sources/EFStorageCore/EFSingleInstanceStorageReference.swift b/Sources/EFStorageCore/EFSingleInstanceStorageReference.swift index b27ce21..a5110f1 100644 --- a/Sources/EFStorageCore/EFSingleInstanceStorageReference.swift +++ b/Sources/EFStorageCore/EFSingleInstanceStorageReference.swift @@ -21,16 +21,38 @@ public protocol EFSingleInstanceStorageReference: AnyObject, EFOptionalContentWr import Foundation -var efStorages = [String: NSMapTable]() -var efStoragesLock = NSLock() +internal enum _EFStorages { + internal typealias Table = [String: NSMapTable] + + private static var _efStorages = Table() { + didSet { + if oldValue.capacity != _efStorages.capacity { + modify(by: organize) + } + } + } + + /// `organize` happens when `lock` is obtained, so it has to be recursive + private static var lock = NSRecursiveLock() -private func organizeEFStorages() { - #warning("需要找一个时机调用来清理不需要的容器") - #warning("Needs performance test once integrated") - efStoragesLock.lock() - defer { efStoragesLock.unlock() } - // http://cocoamine.net/blog/2013/12/13/nsmaptable-and-zeroing-weak-references/ - efStorages = efStorages.filter { $0.value.keyEnumerator().allObjects.count == 0 } + internal static func modify(by mutate: (inout Table) throws -> T) rethrows -> T { + lock.lock() + defer { lock.unlock() } + return try mutate(&_efStorages) + } + + internal static func read(by access: (Table) throws -> T) rethrows -> T { + lock.lock() + defer { lock.unlock() } + return try access(_efStorages) + } + + private static func organize(_ efStorages: inout Table) { + _efStorageLog("CLEAN START \(efStorages.count)") + // http://cocoamine.net/blog/2013/12/13/nsmaptable-and-zeroing-weak-references/ + efStorages = efStorages.filter { !$0.value.keyEnumerator().allObjects.isEmpty } + _efStorageLog("CLEAN AFTER \(efStorages.count)") + } } @inlinable @@ -47,8 +69,13 @@ extension EFSingleInstanceStorageReference { } public static func forKey(_ key: String, in storage: Storage = Storage.makeDefault()) -> Self { - efStoragesLock.lock() - defer { efStoragesLock.unlock() } + return _EFStorages.modify { efStorages in + return make(forKey: key, in: storage, efStorages: &efStorages) + } + } + + private static func make(forKey key: String, in storage: Storage, + efStorages: inout _EFStorages.Table) -> Self { let typeIdentifier = String(describing: self) if efStorages[typeIdentifier] == nil { _efStorageLog("ALLOC \(typeIdentifier)") diff --git a/Tests/EFStorageTests/EFStorageTests.swift b/Tests/EFStorageTests/EFStorageTests.swift index 374d7c8..9599481 100644 --- a/Tests/EFStorageTests/EFStorageTests.swift +++ b/Tests/EFStorageTests/EFStorageTests.swift @@ -59,7 +59,23 @@ final class EFStorageTests: XCTestCase { var storageText: EFStorageUserDefaultsRef = UserDefaults.efStorage.text + private func printValue(_ t: T?, ofType type: T.Type, + or defaultValue: @autoclosure () -> String) { + print("VALUE \(t?.description ?? defaultValue())") + } + func testExample() { + printValue(UserDefaults.efStorage.nonExisting, ofType: CGFloat.self, or: "SHOULD DISAPPEAR") + printValue(UserDefaults.efStorage.nonExisting, ofType: Data.self, or: "SHOULD DISAPPEAR") + printValue(UserDefaults.efStorage.nonExisting, ofType: NSArray.self, or: "SHOULD DISAPPEAR") + printValue(UserDefaults.efStorage.nonExisting, ofType: Int8.self, or: "SHOULD DISAPPEAR") + printValue(UserDefaults.efStorage.nonExisting, ofType: Int16.self, or: "SHOULD DISAPPEAR") + printValue(UserDefaults.efStorage.nonExisting, ofType: Int32.self, or: "SHOULD DISAPPEAR") + printValue(UserDefaults.efStorage.nonExisting, ofType: Int64.self, or: "SHOULD DISAPPEAR") + printValue(UserDefaults.efStorage.nonExisting, ofType: UInt8.self, or: "SHOULD DISAPPEAR") + printValue(UserDefaults.efStorage.nonExisting, ofType: UInt16.self, or: "SHOULD DISAPPEAR") + printValue(UserDefaults.efStorage.nonExisting, ofType: UInt32.self, or: "SHOULD DISAPPEAR") + printValue(UserDefaults.efStorage.nonExisting, ofType: UInt64.self, or: "SHOULD DISAPPEAR") XCTAssertEqual(text, EFStorageTests.defaultText) text = "meow" XCTAssertEqual(_text.wrappedValue, "meow") @@ -70,7 +86,7 @@ final class EFStorageTests: XCTestCase { let hasPaidBeforeRef: EFStorageUserDefaultsRef = UserDefaults.efStorage.oldHasPaidBeforeKey XCTAssertEqual(hasPaidBeforeRef.content, true) XCTAssertEqual(UserDefaults.standard.bool(forKey: "oldHasPaidBeforeKey"), true) - print(efStorages) + _EFStorages.read { print($0) } XCTAssertEqual(hasPaidBefore, true) XCTAssertEqual(mixedType, "1551") mixedType = "Brand New"