OSDN Git Service

saveメソッドを一本化
[kcd/KCD.git] / KCD / CoreDataManager.swift
1 //
2 //  CoreDataManager.swift
3 //  KCD
4 //
5 //  Created by Hori,Masaki on 2017/10/26.
6 //  Copyright © 2017年 Hori,Masaki. All rights reserved.
7 //
8
9 import Cocoa
10
11 enum CoreDataManagerType {
12     
13     case reader
14     case editor
15 }
16
17 enum CoreDataError: Error {
18     
19     case saveLocationIsUnuseable
20     case couldNotCreateModel
21     case couldNotCreateCoordinator(String)
22     case couldNotSave(String)
23 }
24
25 protocol CoreDataProvider {
26     
27     init(type: CoreDataManagerType)
28     
29     static var core: CoreDataCore { get }
30     
31     var context: NSManagedObjectContext { get }
32     
33     func save(errorHandler: @escaping (Error) -> Void)
34 }
35
36 protocol CoreDataAccessor: CoreDataProvider {
37     
38     func sync(execute: () -> Void)
39     func sync<T>(execute: () -> T) -> T
40     func async(execute: @escaping () -> Void)
41     
42     func insertNewObject<T>(for entity: Entity<T>) -> T?
43     func delete(_ object: NSManagedObject)
44     func object<T>(of entity: Entity<T>, with objectId: NSManagedObjectID) -> T?
45     func objects<T>(of entity: Entity<T>, sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?) throws -> [T]
46     
47     func object(with objectId: NSManagedObjectID) -> NSManagedObject
48 }
49
50 protocol CoreDataManager: CoreDataAccessor {
51     
52     static var `default`: Self { get }
53     
54     static func oneTimeEditor() -> Self
55 }
56
57 func presentOnMainThread(_ error: Error) {
58     
59     if Thread.isMainThread {
60         
61         NSApp.presentError(error)
62         
63     } else {
64         
65         DispatchQueue.main.sync {
66             
67             _ = NSApp.presentError(error)
68         }
69     }
70 }
71
72 // MARK: - Extension
73 extension CoreDataProvider {
74     
75     static func context(for type: CoreDataManagerType) -> NSManagedObjectContext {
76         
77         switch type {
78         case .reader: return core.readerContext
79             
80         case .editor: return core.editorContext()
81         }
82     }
83     
84     func save(errorHandler: @escaping (Error) -> Void = presentOnMainThread) {
85         
86         // parentを辿ってsaveしていく
87         func propagateSaveAsync(_ context: NSManagedObjectContext) {
88             
89             guard let parent = context.parent else {
90                 
91                 return
92             }
93             
94             parent.perform {
95                 
96                 do {
97                     
98                     try parent.save()
99                     
100                     propagateSaveAsync(parent)
101                     
102                 } catch {
103                     
104                     errorHandler(error)
105                 }
106             }
107         }
108         
109         context.performAndWait {
110             
111             guard context.commitEditing() else {
112                 
113                 errorHandler(CoreDataError.couldNotSave("Unable to commit editing before saveing"))
114                 return
115             }
116             
117             do {
118                 
119                 try context.save()
120                 
121                 // save reader and writer context async.
122                 // non throw exceptions.
123                 propagateSaveAsync(context)
124                 
125             } catch let error as NSError {
126                 
127                 errorHandler(CoreDataError.couldNotSave(error.localizedDescription))
128             }
129         }
130     }
131 }
132
133 extension CoreDataAccessor {
134     
135     func sync(execute work: () -> Void) {
136         
137         self.context.performAndWait(work)
138     }
139     
140     func sync<T>(execute work: () -> T) -> T {
141         
142         var value: T!
143         sync {
144             value = work()
145         }
146         return value
147     }
148     
149     func async(execute work: @escaping () -> Void) {
150         
151         self.context.perform(work)
152     }
153     
154     func insertNewObject<T>(for entity: Entity<T>) -> T? {
155         
156         return NSEntityDescription.insertNewObject(forEntityName: entity.name, into: context) as? T
157     }
158     
159     func delete(_ object: NSManagedObject) {
160         
161         context.delete(object)
162     }
163     
164     func object<T>(of entity: Entity<T>, with objectId: NSManagedObjectID) -> T? {
165         
166         return context.object(with: objectId) as? T
167     }
168     
169     func objects<T>(of entity: Entity<T>, sortDescriptors: [NSSortDescriptor]? = nil, predicate: NSPredicate? = nil) throws -> [T] {
170         
171         let req = NSFetchRequest<T>(entityName: entity.name)
172         req.sortDescriptors = sortDescriptors
173         req.predicate = predicate
174         
175         var result: [T]?
176         var caughtError: Error?
177         sync {
178             do {
179                 
180                 result = try self.context.fetch(req)
181             } catch {
182                 
183                 caughtError = error
184             }
185         }
186         if let error = caughtError {
187             
188             throw error
189         }
190         
191         return result ?? []
192     }
193     
194     func object(with objectId: NSManagedObjectID) -> NSManagedObject {
195         
196         var result: NSManagedObject?
197         sync {
198             result = self.context.object(with: objectId)
199         }
200         return result!
201     }
202 }
203
204 extension CoreDataManager {
205     
206     static func oneTimeEditor() -> Self {
207         
208         return Self.init(type: .editor)
209     }
210 }