有什么干净的方法可以批量插入具有关系的核心数据对象吗



在插入核心数据对象时,我一直在后台线程中观察到高CPU时间,从分析器中我可以发现,这主要是因为我正在逐个创建一些关系,这些关系可能有数千个。

所以我想如果我可以用批插入创建它们。使用NSBatchInsertRequest,我可以很容易地为使用而不使用关系的对象做到这一点,但有了关系,我似乎找不到任何干净的方法。如果没有关系,我可以使用上述请求轻松创建字典和插入。

对于关系,我还尝试使用NSBatchInsertRequest的对象处理程序方法,但即使这样也会给我一个异常

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Illegal attempt to establish a relationship 'run' between objects in different contexts

这就是我试图确保添加的轨迹点使用与创建的轨迹点相同上下文中的运行对象的方式

func addTrackPoints(run: RunModel, objectId: NSManagedObjectID) async throws {
let locations:[CLLocation] = run.getLocations()
let count = run.getLocations().count
var index = 0
let batchInsert = NSBatchInsertRequest(entity: TrackPoint.entity()) { (managedObject: NSManagedObject) -> Bool in
guard index < count  else { return true }
if let trackPoint = managedObject as? TrackPoint {
let data = locations[index]
guard let run = try? StorageService.shared.getBackgroundContext().object(with: objectId) as? Run else {
fatalError("failed to get run object")
}
trackPoint.run = run
}
index += 1
return false
}
try await StorageService.shared.batchInsert(entity: TrackPoint.entity(), batchInsertRequest: batchInsert, context: StorageService.shared.getBackgroundContext())
}

我也尝试过,没有从同一上下文访问对象,而是直接使用我创建的Run对象。它没有崩溃,但仍然没有建立关系。它还迫使我删除concurrentydebug run参数。

func addTrackPoints(run: RunModel, object: Run) async throws {
let locations = run.getLocations()
let count = run.getLocations().count
var index = 0
let batchInsert = NSBatchInsertRequest(entity: TrackPoint.entity()) { (managedObject: NSManagedObject) -> Bool in
guard index < count  else { return true }
if let trackPoint = managedObject as? TrackPoint {
let data:CLLocation = locations[index]
trackPoint.run = object
}
index += 1
return false
}
try await StorageService.shared.batchInsert(entity: TrackPoint.entity(), batchInsertRequest: batchInsert, context: StorageService.shared.getBackgroundContext()) }

StorageService

public func batchInsert(entity: NSEntityDescription, batchInsertRequest: NSBatchInsertRequest, context: NSManagedObjectContext? = nil) async throws {
var taskContext:NSManagedObjectContext? = context
if(taskContext == nil) {
taskContext = StorageService.shared.newTaskContext()
// Add name and author to identify source of persistent history changes.
taskContext?.name = "importContext"
taskContext?.transactionAuthor = "import(entity.name ?? "entity")"
}
/// - Tag: performAndWait
try await taskContext?.perform {
// Execute the batch insert.
do{
let fetchResult = try taskContext?.execute(batchInsertRequest)
if let batchInsertResult = fetchResult as? NSBatchInsertResult,
let success = batchInsertResult.result as? Bool, success {
return
}
} catch {
self.logger.error("Failed to execute batch insert request. (error)")
}
throw SSError.batchInsertError
}
logger.info("Successfully inserted data for (entity.name ?? "entity")")
}

如有任何帮助,我们将不胜感激:-(

应用程序是如何工作的,我将请求发送到服务器,得到一些结果,并希望数据保存在核心数据中以供进一步使用,只有在需要时才将请求发送给服务器。所以下次我将从数据库中查询数据。

这是样品:

我总是将数据保存在后台上下文中,配置如下:

func getBgContext() -> NSManagedObjectContext {
let bgContext = self.persistenceController.container.newBackgroundContext()
bgContext.automaticallyMergesChangesFromParent = true
bgContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return bgContext
}

接下来,我像这样构建我的数据模型,这样解码器将在dbContext:中处理实体创建和数据解析+插入

public class SomeDataModel: NSManagedObject, Codable {
var entityName: String = "SomeDataModel"
enum CodingKeys: String, CodingKey {
case id = "id"
case someData = "someData"
}
public required convenience init(from decoder: Decoder) throws {
guard
let context = decoder.userInfo[CodingUserInfoKey.managedObjectContext] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "SomeDataModel", in: context)
else {
throw DecoderConfigurationError.missingManagedObjectContext
}
self.init(entity: entity, insertInto: context)
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(Int32.self, forKey: .id)
someData = try values.decodeIfPresent(String.self, forKey: .someData)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(someData, forKey: .someData)
}
func toExternalModel() -> SomeExternalUsableModel {
return SomeExternalUsableModel(id: id, someData: someData)
}
}
extension SomeDataModel {
@nonobjc public class func fetchRequest() -> NSFetchRequest<SomeDataModel> {
return NSFetchRequest<SomeDataModel>(entityName: "SomeDataModel")
}

@NSManaged public var someData: String?
@NSManaged public var id: Int32

}

extension SomeDataModel: Identifiable {
}

要将dbcontext传递给解码器,我要做的下一步:

extension CodingUserInfoKey {
static let managedObjectContext = CodingUserInfoKey(rawValue: "managedObjectContext")!
}

dbContext-在API助手类中的某个位置创建背景上下文,并将此上下文用于下面的所有部分。

接下来,当服务器响应时,我用解码器进行解析:

let model = try self.dbContext.performAndWait {
let jsonDecoder = JSONDecoder()
let jsonEncoder = JSONEncoder()
// pass context to decoder/encoder
jsonDecoder.userInfo[CodingUserInfoKey.managedObjectContext] = self.dbContext
jsonEncoder.userInfo[CodingUserInfoKey.managedObjectContext] = self.dbContext
// parse model, used generic for reuse for other models 
let model = try jsonDecoder.decode(T.self, from: result.data)
// after this line - all the data is parsed from response from server, and saved to dbContext, and contained in model as well
if self.dbContext.hasChanges {
do {
try self.dbContext.save()
self.dbContext.refreshAllObjects() // refresh context objects to ELIMINATE all outdated db objects in memory (specially when you will have relations, they could remain in memory until updated)
} catch let error {
// process error                   
}
}
return model
}
// do with saved and ready to use data in models whatever needed:    
return model

和用于performAndWait 的扩展

extension NSManagedObjectContext {
func performAndWait<T>(_ block: () throws -> T) throws -> T? {
var result: Result<T, Error>?
performAndWait {
result = Result { try block() }
}
return try result?.get()
}
func performAndWait<T>(_ block: () -> T) -> T? {
var result: T?
performAndWait {
result = block()
}
return result
}
}

最新更新