Skip to content

Instantly share code, notes, and snippets.

@xmarkmaxwell
Last active June 16, 2020 20:25
Show Gist options
  • Save xmarkmaxwell/5eaa37aafc0c099ef31ef8300d935dc3 to your computer and use it in GitHub Desktop.
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.
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()
}
}
//
// 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