具有多个目标的核心数据自定义迁移策略

问题描述 投票:0回答:3

如果我想对给定实体使用自定义迁移策略,我相信我必须在类名前添加产品模块名称作为前缀,如下图所示:

如何处理多个目标?

我尝试使用以下条目:

$(PRODUCT_MODULE_NAME).VisitToVisitPolicy
但这似乎不起作用。我仍然有可能复制映射模型,每个目标一个,但这感觉不对。

ios core-data mapping-model
3个回答
2
投票

尝试在应用程序和测试目标之间共享模型文件时遇到同样的问题。几乎放弃了,我想我必须使用你的重复黑客,但幸运的是找到了一个明智的方法:

// Get mapping model
let mappingModel = NSMappingModel(from: [.main], 
                        forSourceModel: sourceModel, 
                      destinationModel: destinationModel)!

// Get migration policy class name that also includes the module name
let fullClassName = NSStringFromClass(NSEntityMigrationPolicySubclass.self)

// Set policy here (I have one policy per migration, so this works)
mappingModel.entityMappings.forEach { 
    $0.entityMigrationPolicyClassName = fullClassName 
}

// Migrate
let manager = NSMigrationManager(sourceModel: sourceModel, 
                            destinationModel: destinationModel)

try! manager.migrateStore(from: sourceURL, 
                    sourceType: NSSQLiteStoreType, 
                       options: nil, 
                          with: mappingModel, 
              toDestinationURL: destinationURL, 
               destinationType: NSSQLiteStoreType, 
            destinationOptions: nil)

0
投票

我也有同样的问题。我的解决方案与 Alexander 的解决方案类似,它应该适用于多种迁移策略(每个实体一个)。您需要将

Custom Policy
设置为没有任何命名空间的类名,并在获取映射模型后我这样做:

    mapping.entityMappings.forEach {
        if let entityMigrationPolicyClassName = $0.entityMigrationPolicyClassName,
            let namespace = Bundle.main.infoDictionary?["CFBundleExecutable"] as? String {
            $0.entityMigrationPolicyClassName = "\(namespace).\(entityMigrationPolicyClassName)"
        }
    }

0
投票

根据之前答案的片段,这里有一个更完整的示例实现。为了做到这一点,您似乎需要进行完全手动迁移。

注意: 我还没有让它与

@FetchRequest
装饰器一起使用,但它正在与基本的 MVVM 设置一起使用。在迁移过程中,我强制更新以解决 WAL 问题,导致实体加载两次出现问题。直接在托管对象上下文上运行获取请求似乎工作正常。

import CoreData

struct DataLayer {
    static let shared = DataLayer()
    let container: NSPersistentContainer
    
    // The base directory for you app's documents
    private var documentsUrl: URL {
        let fileManager = FileManager.default
        
        if let url = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "your.group.here") {
            return url
        } else {
            // Falling back to the regular core data location.
            let urls = fileManager.urls(for: .documentDirectory, in: .userDomainMask)
            return urls.first!
        }
    }

    // The SQLite data store
    private var dataStoreUrl: URL {
        return documentsUrl.appendingPathComponent("YourDataStoreName")
    }

    init() {
        container = NSPersistentContainer(name: "YourDataStoreName")
        
        migrateIfNeeded()
        
        // Prevent Core Data from trying to automatically migrate
        container.persistentStoreDescriptions.first!.url = dataStoreUrl
        container.persistentStoreDescriptions.first!.shouldMigrateStoreAutomatically = false
        container.persistentStoreDescriptions.first!.shouldInferMappingModelAutomatically = false

        // Load the new store just like you normally would
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                print("Core Data failed to load: \(error.localizedDescription)")
            }
        })
        
        container.viewContext.automaticallyMergesChangesFromParent = true
    }

    func getItems() -> [Item] {
        let fetchRequest = NSFetchRequest<Item>(entityName: "Item")
        fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timestamp", ascending: true)]
        
        do {
            return try container.viewContext.fetch(fetchRequest)
        } catch {
            let nsError = error as NSError
            print("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        
        return []
    }

    /// Checks if the current data store is up to date and migrates to the newest version if needed
    func migrateIfNeeded() {
        // The managed object model we might need to migrate to
        let finalManagedObjectModel = NSManagedObjectModel(contentsOf: Bundle.main.url(forResource: "Incremental", withExtension: "momd")!)!
        
        // If the app hasn't ever been launched there might not be a data store at all
        if !FileManager.default.fileExists(atPath: dataStoreUrl.path) {
            print("No store to check")
            return
        }

        // Get metadata from the source data store
        guard let sourceMetadata = try? NSPersistentStoreCoordinator.metadataForPersistentStore(type: .sqlite, at: dataStoreUrl) else {
            fatalError("Could not find metadata for current data store")
        }
        
        // If the current data store is compatable with the desired object model, no need to do anything
        let compatible = finalManagedObjectModel.isConfiguration(withName: nil, compatibleWithStoreMetadata: sourceMetadata)
        if compatible {
            print("compatible - skipping migration")
            return
        }
        
        // Get the object model of the current data store
        let sourceModel = NSManagedObjectModel.mergedModel(from: [Bundle.main], forStoreMetadata: sourceMetadata)!
        
        // Because iOS by default uses WAL to write new data to a SQLite database there's a chance not all data has been written to the
        // main SQLite file. The following will force iOS to write all lingering data to the main file before migrating.
        do {
            var persistentStoreCoordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: sourceModel)

            let options = [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
            let store = try persistentStoreCoordinator!.addPersistentStore(type: .sqlite, at: dataStoreUrl, options: options)
            try persistentStoreCoordinator!.remove(store)
            persistentStoreCoordinator = nil
        } catch let error {
            fatalError("\(error)")
        }
        
        // Search for a mapping model from the current store version to the target version
        // You could also attempt to infer a mapping model here
        guard let mappingModel = NSMappingModel(from: [Bundle.main], forSourceModel: sourceModel, destinationModel: finalManagedObjectModel) else {
            fatalError("Could not find mapping model")
        }
        
        // Fix the migration policies for the current target
        mappingModel.entityMappings.forEach {
            if let entityMigrationPolicyClassName = $0.entityMigrationPolicyClassName,
                let namespace = Bundle.main.infoDictionary?["CFBundleExecutable"] as? String {
                $0.entityMigrationPolicyClassName = "\(namespace).\(entityMigrationPolicyClassName)"
            }
        }
        
        // Set up the migration manager and temporary data store
        let migrationManager = NSMigrationManager(sourceModel: sourceModel, destinationModel: finalManagedObjectModel)
        let tempDataStoreUrl = documentsUrl.appendingPathComponent("TemporaryIncremental.sqlite")
        
        do {
            // Migrate the old data store into the temporary one
            try migrationManager.migrateStore(from: dataStoreUrl, type: .sqlite, options: nil, mapping: mappingModel, to: tempDataStoreUrl, type: .sqlite, options: nil)
            
            // Delete the old data store and move the temporary into the original spot
            try FileManager.default.removeItem(at: dataStoreUrl)
            try FileManager.default.moveItem(at: tempDataStoreUrl, to: dataStoreUrl)
        } catch let error {
            fatalError("\(error)")
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.