Last active
June 16, 2020 20:25
-
-
Save xmarkmaxwell/5eaa37aafc0c099ef31ef8300d935dc3 to your computer and use it in GitHub Desktop.
Easy API to store individual entries in UserDefaults to speed up access in large applications.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extension MyCoordinator: StorableItemOptional { | |
static let storageDB = Storage.Database<ItemType?>(table: storageTable) { storageInitializer() } | |
static let storageTable = Storage.Table(name: "IDENTIFIER") | |
static var storageInitializer: () -> ItemType? { | |
{ nil } | |
} | |
} | |
extension MyCoordinator: StorableItem { | |
static let storageDB = Storage.Database<ItemType>(table: storageTable) { storageInitializer() } | |
static let storageTable = Storage.Table(name: "IDENTIFIER") | |
static var storageInitializer: () -> ItemType { | |
{ ItemType() } | |
} | |
} | |
//// | |
extension MyCoordinator { | |
func storeDatabase() { | |
Self.storageDB.store(item: itemInstance) | |
} | |
func restoreDatabase() { | |
itemInstance = Self.storageDB.restore() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Storage.swift | |
// MaxwellSpex | |
// | |
// Created by mark maxwell on 5/13/20. | |
// Copyright © 2020 maxwell.design. All rights reserved. | |
// | |
import Combine | |
import Foundation | |
protocol StorableItemOptional { | |
associatedtype Item: Codable | |
static var storageDB: Storage.Database<Item?> { get } | |
static var storageTable: Storage.Table { get } | |
static var storageInitializer: () -> Item? { get } | |
} | |
protocol StorableItem { | |
associatedtype Item: Codable | |
static var storageDB: Storage.Database<Item> { get } | |
static var storageTable: Storage.Table { get } | |
static var storageInitializer: () -> Item { get } | |
} | |
class Storage: ObservableObject { | |
static let globalKeyspace = "com.maxwelldesign.v1" | |
class Database<Item: Codable> { | |
var keyspace: String | |
let model: TableModel | |
let builder: () -> Item | |
let queue = DispatchQueue(label: "save.\(UUID().uuidString)") | |
let lock = NSLock() | |
var observer: AnyCancellable? | |
init(keyspace: String = Storage.globalKeyspace, table: Table, initializer: @escaping () -> Item) { | |
self.keyspace = keyspace | |
model = Storage.table(for: table) | |
builder = initializer | |
loadInitialData() | |
setupObserver() | |
} | |
func loadInitialData() { | |
model.table.data = storage.string(forKey: tableKeyspace) | |
} | |
func setupObserver() { | |
observer = model | |
.objectWillChange | |
.throttle(for: .milliseconds(model.table.delayMS), scheduler: RunLoop.main, latest: true) | |
.sink(receiveValue: { [weak self] _ in | |
self?.persist() | |
}) | |
} | |
func store(item: Item) { | |
model.table.data = item.asJSONString() | |
} | |
var storage: UserDefaults { | |
UserDefaults.standard | |
} | |
var tableKeyspace: String { | |
"\(keyspace).\(model.table.name)" | |
} | |
func restore() -> Item { | |
if let stored = model.table.data ?? storage.string(forKey: tableKeyspace) { | |
return Codec.object(fromJSON: stored) ?? builder() | |
} | |
return builder() | |
} | |
func persist() { | |
performInQueue { | |
guard let data = model.table.data else { return } | |
self.storage.set(data, forKey: tableKeyspace) | |
} | |
} | |
func performInQueue(_ closure: () -> Void) { | |
queue.sync { | |
lock.lock() | |
closure() | |
lock.unlock() | |
} | |
} | |
} | |
class TableModel: ObservableObject { | |
@Published var table: Table | |
init(table: Table) { | |
self.table = table | |
} | |
} | |
struct Table { | |
var name: String | |
var data: String? | |
var delayMS = 250 | |
} | |
} | |
extension Storage { | |
@Atomic static var tables: [String: TableModel] = [:] | |
static func table(for table: Storage.Table) -> TableModel { | |
var t = tables | |
defer { | |
tables = t | |
} | |
let result = t[table.name] ?? TableModel(table: table) | |
t[table.name] = result | |
return result | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment