-
Notifications
You must be signed in to change notification settings - Fork 0
Local DB 추가 #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Local DB 추가 #19
Changes from 7 commits
1d6ae19
3e99a69
25c1cbd
e6b566a
4d5d317
a73dad2
9a8a119
7a300d9
0d0fdb3
3ea35e9
165aa7e
e2711e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| @preconcurrency import CoreData | ||
| import os | ||
|
|
||
| public final class ContextManager: CoreDataStack, Sendable { | ||
| private enum Constants: Sendable { | ||
| static let appGroup = "group.msg.booktracker" | ||
| static let cloudContainerIdentifier: String = "iCloud.msg.onmir" | ||
| static let inMemoryStoreURL: URL = URL(fileURLWithPath: "/dev/null") | ||
| static let databaseName: String = "OnmirModel" | ||
| } | ||
|
|
||
| private let modelName: String | ||
| private let storeURL: URL | ||
| private let persistentContainer: NSPersistentCloudKitContainer | ||
|
|
||
| public var mainContext: NSManagedObjectContext { | ||
| persistentContainer.viewContext | ||
| } | ||
|
|
||
| public static let shared: ContextManager = { | ||
| ContextManager( | ||
| modelName: Constants.databaseName, | ||
| store: FileManager.default.containerURL( | ||
| forSecurityApplicationGroupIdentifier: Constants.appGroup | ||
| )! | ||
| ) | ||
| }() | ||
|
|
||
| init(modelName: String, store storeURL: URL) { | ||
| self.modelName = modelName | ||
| self.storeURL = storeURL | ||
| self.persistentContainer = Self.createPersistentContainer( | ||
| storeURL: storeURL, | ||
| modelName: modelName | ||
| ) | ||
|
|
||
| mainContext.automaticallyMergesChangesFromParent = true | ||
| mainContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy | ||
| } | ||
|
|
||
| public func newDerivedContext() -> NSManagedObjectContext { | ||
| let context = persistentContainer.newBackgroundContext() | ||
| context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy | ||
| return context | ||
| } | ||
| } | ||
|
|
||
| extension ContextManager { | ||
| private static func createPersistentContainer( | ||
| storeURL: URL, | ||
| modelName: String | ||
| ) -> NSPersistentCloudKitContainer { | ||
| guard | ||
| let modelFileURL = Bundle.main.url( | ||
| forResource: modelName, | ||
| withExtension: "momd" | ||
| ) | ||
| else { | ||
| fatalError("Can't find \(Constants.databaseName).momd") | ||
| } | ||
|
|
||
| guard | ||
| let objectModel = NSManagedObjectModel(contentsOf: modelFileURL) | ||
| else { | ||
| fatalError( | ||
| "Can't create object model named \(modelName) at \(modelFileURL)" | ||
| ) | ||
| } | ||
|
|
||
| guard | ||
| let stagedMigrationFactory = StagedMigrationFactory( | ||
| bundle: .main, | ||
| momdURL: modelFileURL, | ||
| logger: os.Logger.contextManager | ||
| ) | ||
| else { | ||
| fatalError("Can't create StagedMigrationFactory") | ||
| } | ||
|
|
||
| let baseURL = | ||
| storeURL | ||
| .appendingPathComponent("Onmir", isDirectory: true) | ||
| .appendingPathComponent("CoreData", isDirectory: true) | ||
|
|
||
| if !FileManager.default.fileExists( | ||
| atPath: baseURL.path(percentEncoded: false) | ||
| ) { | ||
| do { | ||
| try FileManager.default.createDirectory( | ||
| at: baseURL, | ||
| withIntermediateDirectories: true | ||
| ) | ||
| } catch { | ||
| os.Logger.contextManager.error("Can't create directory: \(error)") | ||
| } | ||
|
Comment on lines
+91
to
+93
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error handling for directory creation only logs the error. If creating the directory fails, } catch {
fatalError("Can't create directory for Core Data store: \(error)")
} |
||
| } | ||
|
|
||
| let sqliteURL = | ||
| baseURL | ||
| .appending(component: "ONMIR", directoryHint: .notDirectory) | ||
| .appendingPathExtension("sqlite") | ||
|
|
||
| os.Logger.contextManager.debug("\(sqliteURL)") | ||
| let storeDescription = NSPersistentStoreDescription(url: sqliteURL) | ||
| // storeDescription.url = Constants.inMemoryStoreURL | ||
| storeDescription.type = NSSQLiteStoreType | ||
| storeDescription.setOption( | ||
| stagedMigrationFactory.create(), | ||
| forKey: NSPersistentStoreStagedMigrationManagerOptionKey | ||
| ) | ||
| storeDescription.shouldAddStoreAsynchronously = false | ||
|
|
||
| storeDescription.cloudKitContainerOptions = | ||
| NSPersistentCloudKitContainerOptions( | ||
| containerIdentifier: Constants.cloudContainerIdentifier | ||
| ) | ||
| storeDescription.setOption( | ||
| true as NSNumber, | ||
| forKey: NSPersistentHistoryTrackingKey | ||
| ) | ||
| storeDescription.setOption( | ||
| true as NSNumber, | ||
| forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey | ||
| ) | ||
|
|
||
| let persistentContainer = NSPersistentCloudKitContainer( | ||
| name: modelName, | ||
| managedObjectModel: objectModel | ||
| ) | ||
| persistentContainer.persistentStoreDescriptions = [storeDescription] | ||
|
|
||
| persistentContainer.viewContext.automaticallyMergesChangesFromParent = true | ||
|
|
||
| persistentContainer.loadPersistentStores { _, error in | ||
| if let error { | ||
| os.Logger.contextManager.error("\(error)") | ||
|
|
||
| assertionFailure("Can't initialize Core Data stack") | ||
|
baekteun marked this conversation as resolved.
|
||
| } | ||
| } | ||
|
|
||
| return persistentContainer | ||
| } | ||
|
|
||
| private static func storeURL() -> URL { | ||
| guard | ||
| let fileContainer = FileManager.default.containerURL( | ||
| forSecurityApplicationGroupIdentifier: Constants.appGroup | ||
| ) | ||
| else { | ||
| fatalError() | ||
|
baekteun marked this conversation as resolved.
Outdated
|
||
| } | ||
| return fileContainer | ||
| } | ||
| } | ||
|
|
||
| private extension os.Logger { | ||
| static let contextManager = os.Logger(subsystem: Bundle.main.bundleIdentifier ?? "", category: "ContextManager") | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| @_exported import CoreData | ||
|
|
||
| public protocol CoreDataStack: Sendable { | ||
| var mainContext: NSManagedObjectContext { get } | ||
|
|
||
| func newDerivedContext() -> NSManagedObjectContext | ||
|
|
||
| func performAndSaveLock<T>(_ block: sending @escaping (NSManagedObjectContext) throws -> T) throws -> T | ||
| func performAndSave<T>(_ block: sending @escaping (NSManagedObjectContext) throws -> T) async throws -> T | ||
|
|
||
| func performAndSaveLock(_ block: sending @escaping (NSManagedObjectContext) throws -> Void) throws | ||
| func performAndSave(_ block: sending @escaping (NSManagedObjectContext) throws -> Void) async throws | ||
|
|
||
| func performQueryLock<T>(_ block: sending @escaping (NSManagedObjectContext) throws -> T) throws -> T | ||
| func performQuery<T>(_ block: sending @escaping (NSManagedObjectContext) throws -> T) async throws -> T | ||
| } | ||
|
|
||
| extension CoreDataStack { | ||
| public func performAndSaveLock<T>(_ block: sending @escaping (NSManagedObjectContext) throws -> T) throws -> T { | ||
| let context = newDerivedContext() | ||
| return try context.performAndWait { | ||
| let result = try block(context) | ||
|
|
||
| try context.save() | ||
| return result | ||
| } | ||
| } | ||
|
|
||
| public func performAndSave<T>(_ block: sending @escaping (NSManagedObjectContext) throws -> T) async throws -> T { | ||
| let context = newDerivedContext() | ||
| return try await context.perform { | ||
| let result = try block(context) | ||
|
|
||
| try context.save() | ||
| return result | ||
| } | ||
| } | ||
|
|
||
| public func performAndSaveLock(_ block: sending @escaping (NSManagedObjectContext) throws -> Void) throws { | ||
| let context = newDerivedContext() | ||
| try context.performAndWait { | ||
| try block(context) | ||
|
|
||
| try context.save() | ||
| } | ||
| } | ||
|
|
||
| public func performAndSave(_ block: sending @escaping (NSManagedObjectContext) throws -> Void) async throws { | ||
| let context = newDerivedContext() | ||
| try await context.perform { | ||
| try block(context) | ||
|
|
||
| try context.save() | ||
| } | ||
| } | ||
|
|
||
| public func performQueryLock<T>(_ block: sending @escaping (NSManagedObjectContext) throws -> T) throws -> T { | ||
| let context = newDerivedContext() | ||
| return try context.performAndWait { | ||
| try block(context) | ||
| } | ||
| } | ||
|
|
||
| public func performQuery<T>(_ block: sending @escaping (NSManagedObjectContext) throws -> T) async throws -> T { | ||
| let context = newDerivedContext() | ||
| return try await context.perform { | ||
| try block(context) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import CoreData | ||
| import Foundation | ||
| import OSLog | ||
|
|
||
| extension NSManagedObjectModelReference { | ||
| fileprivate convenience init(in database: URL, modelName: String) { | ||
| let modelURL = database.appending(component: "\(modelName).mom") | ||
| guard let model = NSManagedObjectModel(contentsOf: modelURL) else { fatalError() } | ||
|
|
||
| self.init(model: model, versionChecksum: model.versionChecksum) | ||
| } | ||
| } | ||
|
|
||
| struct StagedMigrationFactory: Sendable { | ||
| private let momdURL: URL | ||
| private let logger: os.Logger | ||
|
|
||
| init?( | ||
| bundle: Bundle = .main, | ||
| momdURL: URL, | ||
| logger: os.Logger | ||
| ) { | ||
| self.momdURL = momdURL | ||
| self.logger = logger | ||
| } | ||
|
|
||
| func create() -> NSStagedMigrationManager { | ||
| let allStages: [NSCustomMigrationStage] = [] | ||
|
|
||
| return NSStagedMigrationManager(allStages) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import Foundation | ||
|
|
||
| public enum BookSourceType: String, Sendable { | ||
| case googleBooks = "GOOGLE_BOOKS" | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.