From 33e10f78a05cec02255b335f4aff6343848cade7 Mon Sep 17 00:00:00 2001 From: Muukii Date: Tue, 5 Nov 2019 01:04:58 +0900 Subject: [PATCH 1/5] :evergreen_tree: Update --- FlatStore.xcodeproj/project.pbxproj | 4 + ...03A8D90B-CE32-4076-A881-2DAE0795E48B.plist | 22 +++++ .../Info.plist | 31 +++++++ FlatStore/FlatBatchUpdate.swift | 27 ++++-- FlatStore/FlatStore.swift | 85 +++++++++---------- FlatStore/InMemoryEntityStorage.swift | 81 ++++++++++++++++++ 6 files changed, 194 insertions(+), 56 deletions(-) create mode 100644 FlatStore.xcodeproj/xcshareddata/xcbaselines/4B24C542219600B000932155.xcbaseline/03A8D90B-CE32-4076-A881-2DAE0795E48B.plist create mode 100644 FlatStore/InMemoryEntityStorage.swift diff --git a/FlatStore.xcodeproj/project.pbxproj b/FlatStore.xcodeproj/project.pbxproj index 078bc6a..5cd09d7 100644 --- a/FlatStore.xcodeproj/project.pbxproj +++ b/FlatStore.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 4B24C5582196021300932155 /* FlatBatchUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B24C5552196021100932155 /* FlatBatchUpdate.swift */; }; 4B24C5592196021300932155 /* FlatRef.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B24C5562196021200932155 /* FlatRef.swift */; }; 4B59A5042337E49F0088E921 /* FlatStore+Identifiable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B59A5032337E49F0088E921 /* FlatStore+Identifiable.swift */; }; + 4B68395223708286002FFC5A /* InMemoryEntityStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B68395123708286002FFC5A /* InMemoryEntityStorage.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -39,6 +40,7 @@ 4B24C5552196021100932155 /* FlatBatchUpdate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlatBatchUpdate.swift; sourceTree = ""; }; 4B24C5562196021200932155 /* FlatRef.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlatRef.swift; sourceTree = ""; }; 4B59A5032337E49F0088E921 /* FlatStore+Identifiable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FlatStore+Identifiable.swift"; sourceTree = ""; }; + 4B68395123708286002FFC5A /* InMemoryEntityStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InMemoryEntityStorage.swift; sourceTree = ""; }; 4BF9E4BA21A0202A006AE786 /* Playground.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = Playground.playground; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; /* End PBXFileReference section */ @@ -87,6 +89,7 @@ 4B24C5552196021100932155 /* FlatBatchUpdate.swift */, 4B24C5562196021200932155 /* FlatRef.swift */, 4B24C5542196021100932155 /* FlatStore.swift */, + 4B68395123708286002FFC5A /* InMemoryEntityStorage.swift */, 4B59A5032337E49F0088E921 /* FlatStore+Identifiable.swift */, 4B24C53E219600B000932155 /* Info.plist */, ); @@ -213,6 +216,7 @@ buildActionMask = 2147483647; files = ( 4B24C5582196021300932155 /* FlatBatchUpdate.swift in Sources */, + 4B68395223708286002FFC5A /* InMemoryEntityStorage.swift in Sources */, 4B24C5592196021300932155 /* FlatRef.swift in Sources */, 4B59A5042337E49F0088E921 /* FlatStore+Identifiable.swift in Sources */, 4B24C5572196021200932155 /* FlatStore.swift in Sources */, diff --git a/FlatStore.xcodeproj/xcshareddata/xcbaselines/4B24C542219600B000932155.xcbaseline/03A8D90B-CE32-4076-A881-2DAE0795E48B.plist b/FlatStore.xcodeproj/xcshareddata/xcbaselines/4B24C542219600B000932155.xcbaseline/03A8D90B-CE32-4076-A881-2DAE0795E48B.plist new file mode 100644 index 0000000..7de6c6c --- /dev/null +++ b/FlatStore.xcodeproj/xcshareddata/xcbaselines/4B24C542219600B000932155.xcbaseline/03A8D90B-CE32-4076-A881-2DAE0795E48B.plist @@ -0,0 +1,22 @@ + + + + + classNames + + FlatStoreTests + + testPerformanceExample() + + com.apple.XCTPerformanceMetric_WallClockTime + + baselineAverage + 0.073941 + baselineIntegrationDisplayName + Local Baseline + + + + + + diff --git a/FlatStore.xcodeproj/xcshareddata/xcbaselines/4B24C542219600B000932155.xcbaseline/Info.plist b/FlatStore.xcodeproj/xcshareddata/xcbaselines/4B24C542219600B000932155.xcbaseline/Info.plist index 53282ee..b6147cc 100644 --- a/FlatStore.xcodeproj/xcshareddata/xcbaselines/4B24C542219600B000932155.xcbaseline/Info.plist +++ b/FlatStore.xcodeproj/xcshareddata/xcbaselines/4B24C542219600B000932155.xcbaseline/Info.plist @@ -4,6 +4,37 @@ runDestinationsByUUID + 03A8D90B-CE32-4076-A881-2DAE0795E48B + + localComputer + + busSpeedInMHz + 400 + cpuCount + 1 + cpuKind + 6-Core Intel Core i9 + cpuSpeedInMHz + 2900 + logicalCPUCoresPerPackage + 12 + modelCode + MacBookPro15,3 + physicalCPUCoresPerPackage + 6 + platformIdentifier + com.apple.platform.macosx + + targetArchitecture + x86_64 + targetDevice + + modelCode + iPhone12,5 + platformIdentifier + com.apple.platform.iphonesimulator + + 8D614F5D-019F-4C43-82A7-F29441FAEA6C localComputer diff --git a/FlatStore/FlatBatchUpdate.swift b/FlatStore/FlatBatchUpdate.swift index 7655e56..39e2758 100644 --- a/FlatStore/FlatBatchUpdate.swift +++ b/FlatStore/FlatBatchUpdate.swift @@ -26,7 +26,8 @@ public final class FlatBatchUpdatesContext { // private let store: FlatStore - var buffer: [FlatStoreAnyIdentifier : Any] = [:] + var buffer = InMemoryEntityStorage() + private let store: FlatStore public init(store: FlatStore) { @@ -35,21 +36,29 @@ public final class FlatBatchUpdatesContext { public func set(value: T) -> FlatStoreObjectIdentifier { let key = value.id - buffer[key.asAny] = value + buffer.update(inTable: T.FlatStoreID.tableName) { (table) in + table.byID[key.id] = value + } return key } - public func set(values: T) -> [FlatStoreObjectIdentifier] where T.Element : FlatStoreObjectType { - return - values.map { value -> FlatStoreObjectIdentifier in - let key = value.id - buffer[key.asAny] = value - return key + public func set(values: T) -> [FlatStoreObjectIdentifier] where T.Element : FlatStoreObjectType { + + var ids: [FlatStoreObjectIdentifier] = [] + + buffer.update(inTable: T.Element.FlatStoreID.tableName) { (table) in + values.forEach { value in + table.byID[value.id] = value + ids.append(value.id) + } } + + return ids } public func get(by key: FlatStoreObjectIdentifier) -> T? { - if let transientObject = (buffer[key.asAny] as? T) { + + if let transientObject = (buffer.table(name: T.FlatStoreID.tableName)?.byID[key.id] as? T) { return transientObject } if let object = store.get(by: key) { diff --git a/FlatStore/FlatStore.swift b/FlatStore/FlatStore.swift index b67d6a7..b4a8532 100644 --- a/FlatStore/FlatStore.swift +++ b/FlatStore/FlatStore.swift @@ -23,14 +23,14 @@ import Foundation public struct FlatStoreAnyIdentifier : Hashable { - public let typeName: String - public let raw: AnyHashable + public let tableName: String + public let id: AnyHashable public let notificationName: Notification.Name - public init(typeName: String, rawID: AnyHashable) { - self.typeName = typeName - self.raw = rawID - self.notificationName = .init(rawValue: "\(typeName)|\(rawID)") + public init(tableName: String, id: AnyHashable) { + self.tableName = tableName + self.id = id + self.notificationName = .init(rawValue: "\(tableName)|\(id)") } } @@ -41,6 +41,10 @@ public struct FlatStoreObjectIdentifier : Hashable, Cus return raw } + public static var tableName: String { + typeName + } + public static var typeName: String { String.init(reflecting: T.self) } @@ -58,7 +62,10 @@ public struct FlatStoreObjectIdentifier : Hashable, Cus public init(_ raw: T.RawIDType) { self.raw = raw - self.asAny = FlatStoreAnyIdentifier(typeName: Self.typeName, rawID: .init(raw)) + self.asAny = FlatStoreAnyIdentifier( + tableName: Self.typeName, + id: .init(raw) + ) } public var description: String { @@ -113,33 +120,25 @@ extension FlatStoreObjectType { } -public protocol PersistentStoreType { - - typealias Storage = [FlatStoreAnyIdentifier : Any] - - var rawStorage: Storage { get } - var allItemCount: Int { get } - - func updateStorage(_ update: (inout Storage) -> ()) - +public struct EntityTable { + public var byID: [AnyHashable : Any] = [:] } -public final class InMemoryPersistentStore: PersistentStoreType { +public protocol EntityStorageType { - public var allItemCount: Int { - return rawStorage.count - } + typealias Storage = [AnyHashable : EntityTable] - public var rawStorage: Storage = [:] + var allItemCount: Int { get } - public init() { - - } + func allItems() -> [Any] - public func updateStorage(_ update: (inout Storage) -> ()) { - update(&rawStorage) - } + mutating func update(inTable name: String, update: (inout EntityTable) -> Void) + + func table(name: String) -> EntityTable? + mutating func removeAll() + + mutating func merge(inMemoryStorage: InMemoryEntityStorage) } // MARK: FlatStore @@ -150,7 +149,7 @@ open class FlatStore { storage.allItemCount } - private let storage: PersistentStoreType + private var storage: EntityStorageType private let notificationCenter: NotificationCenter = .init() @@ -160,7 +159,7 @@ open class FlatStore { private let lock = NSRecursiveLock() - public init(persistentStore: PersistentStoreType = InMemoryPersistentStore()) { + public init(persistentStore: EntityStorageType = InMemoryEntityStorage()) { self.storage = persistentStore notificationQueue.maxConcurrentOperationCount = 1 } @@ -177,19 +176,19 @@ extension FlatStore { public func get(by id: FlatStoreObjectIdentifier) -> T? { lock.lock(); defer { lock.unlock() } - return storage.rawStorage[id.asAny] as? T + return storage.table(name: T.FlatStoreID.tableName)?.byID[id.id] as? T } public func get(by ids: S) -> [T] where S.Element == FlatStoreObjectIdentifier { lock.lock(); defer { lock.unlock() } return ids.compactMap { key in - storage.rawStorage[key.asAny] as? T + storage.table(name: T.FlatStoreID.tableName)?.byID[key] as? T } } public func get(by id: FlatStoreAnyIdentifier) -> Any? { lock.lock(); defer { lock.unlock() } - return storage.rawStorage[id] + return storage.table(name: id.tableName)?.byID[id.id] } @discardableResult @@ -198,8 +197,8 @@ extension FlatStore { let key = value.id lock.lock() - storage.updateStorage { (store) in - _ = store[key.asAny] = value + storage.update(inTable: T.FlatStoreID.tableName) { (table) in + table.byID[key.id] = value } lock.unlock() @@ -226,8 +225,8 @@ extension FlatStore { let key = value.id lock.lock() - storage.updateStorage { (store) in - _ = store.removeValue(forKey: key.asAny) + storage.update(inTable: T.FlatStoreID.tableName) { (table) in + table.byID.removeValue(forKey: key.asAny) } lock.unlock() @@ -239,9 +238,7 @@ extension FlatStore { public func deleteAll() { lock.lock(); defer { lock.unlock() } - storage.updateStorage { (store) in - _ = store.removeAll() - } + storage.removeAll() } } @@ -251,8 +248,7 @@ extension FlatStore { public func allObjects(type: T.Type) -> [T] { lock.lock(); defer { lock.unlock() } - let typeName = T.FlatStoreID.typeName - return storage.rawStorage.filter { $0.key.typeName == typeName }.map { $0.value } as! [T] + return storage.table(name: T.FlatStoreID.tableName)?.byID.map { $0.value } as! [T] } } @@ -389,12 +385,7 @@ extension FlatStore { let context = FlatBatchUpdatesContext(store: self) let u = try updates(self, context) - - storage.updateStorage { (store) in - for item in context.buffer { - store[item.key] = item.value - } - } + storage.merge(inMemoryStorage: context.buffer) return u } diff --git a/FlatStore/InMemoryEntityStorage.swift b/FlatStore/InMemoryEntityStorage.swift new file mode 100644 index 0000000..b5903f0 --- /dev/null +++ b/FlatStore/InMemoryEntityStorage.swift @@ -0,0 +1,81 @@ +// +// InMemoryEntityStorage.swift +// FlatStore +// +// Created by muukii on 2019/11/05. +// Copyright © 2019 eure. All rights reserved. +// + +import Foundation + +public struct InMemoryEntityStorage: EntityStorageType { + + public var allItemCount: Int { + backingStore.reduce(0) { (count, arg1) -> Int in + let (_, value) = arg1 + return count + value.byID.count + } + } + + private var backingStore: Storage = [:] + private let _lock = NSRecursiveLock() + + public init() { + + } + + public mutating func update(inTable name: String, update: (inout EntityTable) -> Void) { + if backingStore[name] != nil { + update(&backingStore[name]!) + } else { + var table = EntityTable() + update(&table) + backingStore[name] = table + } + } + + public func table(name: String) -> EntityTable? { + backingStore[name] + } + + public mutating func removeAll() { + backingStore = [:] + } + + public func allItems() -> [Any] { + backingStore.reduce(into: [Any]()) { (r, arg1) in + let (_, value) = arg1 + r.append(value.byID.map { $0.value }) + } + } + + public mutating func merge(inMemoryStorage: InMemoryEntityStorage) { + + inMemoryStorage.backingStore.forEach { key, value in + if var table = backingStore[key] { + table.byID.merge(value.byID, uniquingKeysWith: { _, new in new }) + } else { + backingStore[key] = value + } + } + } + +} + +extension InMemoryEntityStorage { + + public struct Getter { + + let storage: InMemoryEntityStorage + + public init(storage: InMemoryEntityStorage) { + self.storage = storage + } + + public func find(by id: Entity.FlatStoreID) -> Entity? { + storage.table(name: Entity.FlatStoreID.tableName)?.byID[id.id] as? Entity + } + + } + +} From 63917d0aca5a51d5a1a9d7962c61d037d03cfef7 Mon Sep 17 00:00:00 2001 From: Muukii Date: Sun, 10 Nov 2019 03:49:46 +0900 Subject: [PATCH 2/5] Dispatch notification --- FlatStore/FlatStore.swift | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/FlatStore/FlatStore.swift b/FlatStore/FlatStore.swift index b4a8532..d482644 100644 --- a/FlatStore/FlatStore.swift +++ b/FlatStore/FlatStore.swift @@ -99,7 +99,12 @@ public final class FlatStoreNotificationToken : NotificationTokenType { } } -public protocol FlatStoreObjectType { +public protocol _FlatStoreObjectType { + + var notificationName: Notification.Name { get } +} + +public protocol FlatStoreObjectType: _FlatStoreObjectType { associatedtype RawIDType : Hashable var rawID: RawIDType { get } @@ -118,6 +123,10 @@ extension FlatStoreObjectType { return FlatStoreObjectIdentifier.init(rawID) } + public var notificationName: Notification.Name { + id.notificationName + } + } public struct EntityTable { @@ -229,17 +238,21 @@ extension FlatStore { table.byID.removeValue(forKey: key.asAny) } lock.unlock() - - let notification = makeSeparatedNotificationName(key.notificationName) - notificationQueue.addOperation { - self.notificationCenter.post(name: notification, object: value) - } + + dispatchUpdateNotification(name: key.notificationName, value: value) } public func deleteAll() { lock.lock(); defer { lock.unlock() } storage.removeAll() } + + func dispatchUpdateNotification(name: Notification.Name, value: Any) { + let notification = makeSeparatedNotificationName(name) + notificationQueue.addOperation { + self.notificationCenter.post(name: notification, object: value) + } + } } @@ -385,7 +398,13 @@ extension FlatStore { let context = FlatBatchUpdatesContext(store: self) let u = try updates(self, context) + storage.merge(inMemoryStorage: context.buffer) + + for item in context.buffer.allItems() as! [_FlatStoreObjectType] { + dispatchUpdateNotification(name: item.notificationName, value: item) + } + return u } From 97736148bde76ec69edb3545b06b944d5272c7bf Mon Sep 17 00:00:00 2001 From: Muukii Date: Sun, 10 Nov 2019 04:14:44 +0900 Subject: [PATCH 3/5] :evergreen_tree: Update --- FlatStore/InMemoryEntityStorage.swift | 3 ++- FlatStoreTests/FlatStoreTests.swift | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/FlatStore/InMemoryEntityStorage.swift b/FlatStore/InMemoryEntityStorage.swift index b5903f0..c4a0d4b 100644 --- a/FlatStore/InMemoryEntityStorage.swift +++ b/FlatStore/InMemoryEntityStorage.swift @@ -45,7 +45,8 @@ public struct InMemoryEntityStorage: EntityStorageType { public func allItems() -> [Any] { backingStore.reduce(into: [Any]()) { (r, arg1) in let (_, value) = arg1 - r.append(value.byID.map { $0.value }) + let items = value.byID.map { $0.value } + r.append(contentsOf: items) } } diff --git a/FlatStoreTests/FlatStoreTests.swift b/FlatStoreTests/FlatStoreTests.swift index e6e7f3a..da8f92e 100644 --- a/FlatStoreTests/FlatStoreTests.swift +++ b/FlatStoreTests/FlatStoreTests.swift @@ -162,6 +162,18 @@ class FlatStoreTests: XCTestCase { XCTAssertEqual(ref.value, comment) } + + func testNestedObject() { + + let user = User(name: "Hey") + let comment = Comment(rawID: "commen_1", userID: user.id) + + storeA.performBatchUpdates { (store, context) -> Void in + _ = context.set(value: user) + _ = context.set(value: comment) + } + + } func testNotificationDifferentStore() { From 2dee97a1581e893ab91e96f358d287b6b25ee746 Mon Sep 17 00:00:00 2001 From: Muukii Date: Sun, 10 Nov 2019 05:26:04 +0900 Subject: [PATCH 4/5] :evergreen_tree: Update --- FlatStore/FlatBatchUpdate.swift | 6 +++--- FlatStore/FlatStore.swift | 8 ++++---- FlatStore/InMemoryEntityStorage.swift | 11 +++++++++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/FlatStore/FlatBatchUpdate.swift b/FlatStore/FlatBatchUpdate.swift index 39e2758..b4eb119 100644 --- a/FlatStore/FlatBatchUpdate.swift +++ b/FlatStore/FlatBatchUpdate.swift @@ -37,7 +37,7 @@ public final class FlatBatchUpdatesContext { public func set(value: T) -> FlatStoreObjectIdentifier { let key = value.id buffer.update(inTable: T.FlatStoreID.tableName) { (table) in - table.byID[key.id] = value + table.byID[key.raw] = value } return key } @@ -48,7 +48,7 @@ public final class FlatBatchUpdatesContext { buffer.update(inTable: T.Element.FlatStoreID.tableName) { (table) in values.forEach { value in - table.byID[value.id] = value + table.byID[value.id.raw] = value ids.append(value.id) } } @@ -58,7 +58,7 @@ public final class FlatBatchUpdatesContext { public func get(by key: FlatStoreObjectIdentifier) -> T? { - if let transientObject = (buffer.table(name: T.FlatStoreID.tableName)?.byID[key.id] as? T) { + if let transientObject = (buffer.table(name: T.FlatStoreID.tableName)?.byID[key.raw] as? T) { return transientObject } if let object = store.get(by: key) { diff --git a/FlatStore/FlatStore.swift b/FlatStore/FlatStore.swift index d482644..811c9e8 100644 --- a/FlatStore/FlatStore.swift +++ b/FlatStore/FlatStore.swift @@ -185,7 +185,7 @@ extension FlatStore { public func get(by id: FlatStoreObjectIdentifier) -> T? { lock.lock(); defer { lock.unlock() } - return storage.table(name: T.FlatStoreID.tableName)?.byID[id.id] as? T + return storage.table(name: T.FlatStoreID.tableName)?.byID[id.raw] as? T } public func get(by ids: S) -> [T] where S.Element == FlatStoreObjectIdentifier { @@ -203,15 +203,15 @@ extension FlatStore { @discardableResult public func set(value: T) -> T.CachingRef { - let key = value.id + let id = value.id lock.lock() storage.update(inTable: T.FlatStoreID.tableName) { (table) in - table.byID[key.id] = value + table.byID[id.raw] = value } lock.unlock() - let notification = makeSeparatedNotificationName(key.notificationName) + let notification = makeSeparatedNotificationName(id.notificationName) notificationQueue.addOperation { self.notificationCenter.post(name: notification, object: value) } diff --git a/FlatStore/InMemoryEntityStorage.swift b/FlatStore/InMemoryEntityStorage.swift index c4a0d4b..26915da 100644 --- a/FlatStore/InMemoryEntityStorage.swift +++ b/FlatStore/InMemoryEntityStorage.swift @@ -54,7 +54,14 @@ public struct InMemoryEntityStorage: EntityStorageType { inMemoryStorage.backingStore.forEach { key, value in if var table = backingStore[key] { - table.byID.merge(value.byID, uniquingKeysWith: { _, new in new }) + var merged = table.byID + + value.byID.forEach { key, value in + merged[key] = value + } + + table.byID = merged + backingStore[key] = table } else { backingStore[key] = value } @@ -74,7 +81,7 @@ extension InMemoryEntityStorage { } public func find(by id: Entity.FlatStoreID) -> Entity? { - storage.table(name: Entity.FlatStoreID.tableName)?.byID[id.id] as? Entity + storage.table(name: Entity.FlatStoreID.tableName)?.byID[id.raw] as? Entity } } From dd3cb18f3e900ea505b8157a4692a58fa482ad28 Mon Sep 17 00:00:00 2001 From: Muukii Date: Sun, 10 Nov 2019 05:42:45 +0900 Subject: [PATCH 5/5] :evergreen_tree: Update --- FlatStore/FlatStore.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FlatStore/FlatStore.swift b/FlatStore/FlatStore.swift index 811c9e8..be8e957 100644 --- a/FlatStore/FlatStore.swift +++ b/FlatStore/FlatStore.swift @@ -191,7 +191,7 @@ extension FlatStore { public func get(by ids: S) -> [T] where S.Element == FlatStoreObjectIdentifier { lock.lock(); defer { lock.unlock() } return ids.compactMap { key in - storage.table(name: T.FlatStoreID.tableName)?.byID[key] as? T + storage.table(name: T.FlatStoreID.tableName)?.byID[key.raw] as? T } } @@ -235,7 +235,7 @@ extension FlatStore { lock.lock() storage.update(inTable: T.FlatStoreID.tableName) { (table) in - table.byID.removeValue(forKey: key.asAny) + table.byID.removeValue(forKey: key.raw) } lock.unlock()