mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-03-25 10:51:41 +01:00
refactor: clean up persistence related code
This commit is contained in:
@@ -7,6 +7,9 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
EC3565522BEFC65F00A4E0BF /* NSManagedObjectContext++.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3565512BEFC65F00A4E0BF /* NSManagedObjectContext++.swift */; };
|
||||
EC3565542BEFC6AD00A4E0BF /* View++.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3565532BEFC6AD00A4E0BF /* View++.swift */; };
|
||||
EC3565562BEFC7B300A4E0BF /* NSManagedObject++.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3565552BEFC7B300A4E0BF /* NSManagedObject++.swift */; };
|
||||
EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC4538882BEBCAE000A86FEC /* Quad.swift */; };
|
||||
EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */; };
|
||||
EC7F6BF02BE5E6E400A34A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */; };
|
||||
@@ -74,6 +77,9 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
EC3565512BEFC65F00A4E0BF /* NSManagedObjectContext++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext++.swift"; sourceTree = "<group>"; };
|
||||
EC3565532BEFC6AD00A4E0BF /* View++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View++.swift"; sourceTree = "<group>"; };
|
||||
EC3565552BEFC7B300A4E0BF /* NSManagedObject++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObject++.swift"; sourceTree = "<group>"; };
|
||||
EC4538882BEBCAE000A86FEC /* Quad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quad.swift; sourceTree = "<group>"; };
|
||||
EC7F6BE82BE5E6E300A34A7B /* Memola.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Memola.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemolaApp.swift; sourceTree = "<group>"; };
|
||||
@@ -339,6 +345,9 @@
|
||||
ECA738E72BE6120F00A4542E /* Color++.swift */,
|
||||
ECA738F52BE612B700A4542E /* MTLDevice++.swift */,
|
||||
ECA738ED2BE6125D00A4542E /* simd_float4x4++.swift */,
|
||||
EC3565512BEFC65F00A4E0BF /* NSManagedObjectContext++.swift */,
|
||||
EC3565532BEFC6AD00A4E0BF /* View++.swift */,
|
||||
EC3565552BEFC7B300A4E0BF /* NSManagedObject++.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@@ -594,6 +603,7 @@
|
||||
ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */,
|
||||
ECA7387A2BE5EF0400A4542E /* MemosView.swift in Sources */,
|
||||
ECA738BA2BE60DEF00A4542E /* HistoryAction.swift in Sources */,
|
||||
EC3565522BEFC65F00A4E0BF /* NSManagedObjectContext++.swift in Sources */,
|
||||
ECFA15222BEF21F500455818 /* CanvasObject.swift in Sources */,
|
||||
ECA738AA2BE6026D00A4542E /* Uniforms.swift in Sources */,
|
||||
ECA7387D2BE5EF4B00A4542E /* MemoView.swift in Sources */,
|
||||
@@ -604,6 +614,7 @@
|
||||
ECA738EA2BE6122E00A4542E /* CGPoint++.swift in Sources */,
|
||||
ECA738A62BE6023F00A4542E /* GridUniforms.swift in Sources */,
|
||||
ECA738D72BE60FC100A4542E /* SolidPointStrokeGenerator.swift in Sources */,
|
||||
EC3565542BEFC6AD00A4E0BF /* View++.swift in Sources */,
|
||||
ECA738832BE5FEFE00A4542E /* RenderPass.swift in Sources */,
|
||||
ECEC01A82BEE11BA006DA24C /* QuadShape.swift in Sources */,
|
||||
ECA738862BE5FF2500A4542E /* Canvas.swift in Sources */,
|
||||
@@ -618,6 +629,7 @@
|
||||
ECA739012BE61D9C00A4542E /* MemolaModel.xcdatamodeld in Sources */,
|
||||
ECA738F02BE6127700A4542E /* CGSize++.swift in Sources */,
|
||||
ECFA15242BEF223300455818 /* GraphicContextObject.swift in Sources */,
|
||||
EC3565562BEFC7B300A4E0BF /* NSManagedObject++.swift in Sources */,
|
||||
ECA738EC2BE6124E00A4542E /* CGAffineTransform++.swift in Sources */,
|
||||
ECA738E22BE610D000A4542E /* GraphicRenderPass.swift in Sources */,
|
||||
ECA738DC2BE6108D00A4542E /* StrokeRenderPass.swift in Sources */,
|
||||
|
||||
@@ -12,7 +12,7 @@ struct MemolaApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
MemosView()
|
||||
.environment(\.managedObjectContext, Persistence.context)
|
||||
.persistence(\.viewContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,9 +39,9 @@ final class GraphicContext: @unchecked Sendable {
|
||||
func undoGraphic() {
|
||||
guard !strokes.isEmpty else { return }
|
||||
let stroke = strokes.removeLast()
|
||||
Persistence.backgroundContext.perform {
|
||||
withPersistence(\.backgroundContext) { [stroke] context in
|
||||
stroke.object?.graphicContext = nil
|
||||
Persistence.saveIfNeededInBackground()
|
||||
try context.saveIfNeeded()
|
||||
}
|
||||
previousStroke = nil
|
||||
}
|
||||
@@ -50,9 +50,9 @@ final class GraphicContext: @unchecked Sendable {
|
||||
switch event {
|
||||
case .stroke(let stroke):
|
||||
strokes.append(stroke)
|
||||
Persistence.backgroundContext.perform { [weak self] in
|
||||
withPersistence(\.backgroundContext) { [weak self, stroke] context in
|
||||
stroke.object?.graphicContext = self?.object
|
||||
Persistence.saveIfNeededInBackground()
|
||||
try context.saveIfNeeded()
|
||||
}
|
||||
previousStroke = nil
|
||||
}
|
||||
@@ -95,8 +95,8 @@ extension GraphicContext {
|
||||
createdAt: .now,
|
||||
thickness: pen.thickness
|
||||
)
|
||||
Persistence.backgroundContext.perform { [graphicContext = object, _stroke = stroke] in
|
||||
let stroke = StrokeObject(context: Persistence.backgroundContext)
|
||||
withPersistence(\.backgroundContext) { [graphicContext = object, _stroke = stroke] context in
|
||||
let stroke = StrokeObject(\.backgroundContext)
|
||||
stroke.color = _stroke.color
|
||||
stroke.style = _stroke.style
|
||||
stroke.thickness = _stroke.thickness
|
||||
@@ -125,12 +125,12 @@ extension GraphicContext {
|
||||
currentStroke.finish(at: point)
|
||||
let saveIndex = currentStroke.batchIndex
|
||||
let quads = Array(currentStroke.quads[saveIndex..<currentStroke.quads.count])
|
||||
Persistence.backgroundContext.perform { [currentStroke, quads] in
|
||||
withPersistence(\.backgroundContext) { [currentStroke, quads] context in
|
||||
currentStroke.saveQuads(for: quads)
|
||||
Persistence.saveIfNeededInBackground()
|
||||
try context.saveIfNeeded()
|
||||
if let stroke = currentStroke.object {
|
||||
currentStroke.quads.removeAll()
|
||||
Persistence.backgroundContext.refresh(stroke, mergeChanges: false)
|
||||
context.refresh(stroke, mergeChanges: false)
|
||||
}
|
||||
}
|
||||
previousStroke = currentStroke
|
||||
@@ -141,12 +141,12 @@ extension GraphicContext {
|
||||
func cancelStroke() {
|
||||
if !strokes.isEmpty {
|
||||
let stroke = strokes.removeLast()
|
||||
Persistence.backgroundContext.perform { [graphicContext = object, _stroke = stroke] in
|
||||
withPersistence(\.backgroundContext) { [graphicContext = object, _stroke = stroke] context in
|
||||
if let stroke = _stroke.object {
|
||||
graphicContext?.strokes.remove(stroke)
|
||||
Persistence.backgroundContext.delete(stroke)
|
||||
context.delete(stroke)
|
||||
}
|
||||
Persistence.saveIfNeededInBackground()
|
||||
try context.saveIfNeeded()
|
||||
}
|
||||
}
|
||||
currentStroke = nil
|
||||
|
||||
@@ -44,17 +44,17 @@ final class Canvas: ObservableObject, Identifiable, @unchecked Sendable {
|
||||
// MARK: - Actions
|
||||
extension Canvas {
|
||||
func load() {
|
||||
Persistence.backgroundContext.perform { [weak self, canvasID] in
|
||||
withPersistence(\.backgroundContext) { [weak self, canvasID] context in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.state = .loading
|
||||
}
|
||||
guard let canvas = Persistence.backgroundContext.object(with: canvasID) as? CanvasObject else {
|
||||
guard let canvas = context.object(with: canvasID) as? CanvasObject else {
|
||||
return
|
||||
}
|
||||
let graphicContext = canvas.graphicContext
|
||||
self?.graphicContext.object = graphicContext
|
||||
self?.graphicContext.load()
|
||||
Persistence.backgroundContext.refresh(canvas, mergeChanges: false)
|
||||
context.refresh(canvas, mergeChanges: false)
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.state = .loaded
|
||||
}
|
||||
|
||||
@@ -104,14 +104,14 @@ final class Stroke: @unchecked Sendable {
|
||||
quads.removeLast(dropCount)
|
||||
let quads = Array(quads[batchIndex..<index])
|
||||
batchIndex = index
|
||||
Persistence.backgroundContext.perform { [weak self, quads] in
|
||||
withPersistence(\.backgroundContext) { [weak self, quads] context in
|
||||
self?.saveQuads(for: quads)
|
||||
}
|
||||
}
|
||||
|
||||
func saveQuads(for quads: [Quad]) {
|
||||
for _quad in quads {
|
||||
let quad = QuadObject(context: Persistence.backgroundContext)
|
||||
let quad = QuadObject(\.backgroundContext)
|
||||
quad.originX = _quad.originX
|
||||
quad.originY = _quad.originY
|
||||
quad.size = _quad.size
|
||||
|
||||
@@ -52,14 +52,14 @@ class History: ObservableObject {
|
||||
for event in redoStack {
|
||||
switch event {
|
||||
case .stroke(let _stroke):
|
||||
Persistence.backgroundContext.perform {
|
||||
withPersistence(\.backgroundContext) { context in
|
||||
if let stroke = _stroke.object {
|
||||
Persistence.backgroundContext.delete(stroke)
|
||||
context.delete(stroke)
|
||||
}
|
||||
try context.saveIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
Persistence.saveIfNeeded()
|
||||
redoStack.removeAll()
|
||||
}
|
||||
|
||||
|
||||
15
Memola/Extensions/NSManagedObject++.swift
Normal file
15
Memola/Extensions/NSManagedObject++.swift
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// NSManagedObject++.swift
|
||||
// Memola
|
||||
//
|
||||
// Created by Dscyre Scotti on 5/11/24.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
extension NSManagedObject {
|
||||
convenience init(_ keyPath: KeyPath<Persistence, NSManagedObjectContext>) {
|
||||
self.init(context: Persistence.shared[keyPath: keyPath])
|
||||
}
|
||||
}
|
||||
16
Memola/Extensions/NSManagedObjectContext++.swift
Normal file
16
Memola/Extensions/NSManagedObjectContext++.swift
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// NSManagedObjectContext++.swift
|
||||
// Memola
|
||||
//
|
||||
// Created by Dscyre Scotti on 5/11/24.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
extension NSManagedObjectContext {
|
||||
func saveIfNeeded() throws {
|
||||
if hasChanges {
|
||||
try save()
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Memola/Extensions/View++.swift
Normal file
16
Memola/Extensions/View++.swift
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// View++.swift
|
||||
// Memola
|
||||
//
|
||||
// Created by Dscyre Scotti on 5/11/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
extension View {
|
||||
func persistence(_ keyPath: KeyPath<Persistence, NSManagedObjectContext>) -> some View {
|
||||
environment(\.managedObjectContext, Persistence.shared[keyPath: keyPath])
|
||||
}
|
||||
}
|
||||
@@ -8,29 +8,24 @@
|
||||
import CoreData
|
||||
import Foundation
|
||||
|
||||
class Persistence {
|
||||
final class Persistence {
|
||||
private let modelName = "MemolaModel"
|
||||
|
||||
static let shared: Persistence = Persistence()
|
||||
|
||||
private init() { }
|
||||
|
||||
static var context: NSManagedObjectContext = {
|
||||
shared.persistentContainer.viewContext
|
||||
}()
|
||||
|
||||
static var backgroundContext: NSManagedObjectContext = {
|
||||
let context = shared.persistentContainer.newBackgroundContext()
|
||||
context.undoManager = nil
|
||||
|
||||
// context.automaticallyMergesC hangesFromParent = true
|
||||
return context
|
||||
}()
|
||||
|
||||
private lazy var viewContext: NSManagedObjectContext = {
|
||||
lazy var viewContext: NSManagedObjectContext = {
|
||||
persistentContainer.viewContext
|
||||
}()
|
||||
|
||||
lazy var backgroundContext: NSManagedObjectContext = {
|
||||
let context = persistentContainer.newBackgroundContext()
|
||||
context.undoManager = nil
|
||||
context.automaticallyMergesChangesFromParent = true
|
||||
return context
|
||||
}()
|
||||
|
||||
lazy var persistentContainer: NSPersistentContainer = {
|
||||
let persistentStore = NSPersistentStoreDescription()
|
||||
persistentStore.shouldMigrateStoreAutomatically = true
|
||||
@@ -75,38 +70,16 @@ class Persistence {
|
||||
fatalError("[Memola]: \(error.localizedDescription)")
|
||||
}
|
||||
}()
|
||||
|
||||
static func performe(_ action: (NSManagedObjectContext) -> Void) {
|
||||
action(shared.viewContext)
|
||||
}
|
||||
|
||||
static func saveIfNeeded() {
|
||||
if context.hasChanges {
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
NSLog("[Memola] - \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func saveIfNeededInBackground() {
|
||||
if backgroundContext.hasChanges {
|
||||
do {
|
||||
try backgroundContext.save()
|
||||
} catch {
|
||||
NSLog("[Memola] - \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Persistence {
|
||||
static func background(_ task: @escaping (NSManagedObjectContext) async throws -> Void, errorHandler: ((Error) async -> Void)? = nil) async {
|
||||
// MARK: - Global Method
|
||||
func withPersistence(_ keypath: KeyPath<Persistence, NSManagedObjectContext>, _ task: @escaping (NSManagedObjectContext) throws -> Void) {
|
||||
let context = Persistence.shared[keyPath: keypath]
|
||||
context.perform {
|
||||
do {
|
||||
try await task(backgroundContext)
|
||||
try task(context)
|
||||
} catch {
|
||||
await errorHandler?(error)
|
||||
NSLog("[Memola] - \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user