I'm trying to import some basic stop data for the trains in Chicago and there are two different custom Core Data types that I'm using to represent that data.
There are Station
objects and Stop
objects. The Station
objects represent an entire station and the Stop
objects represent a grouping of directions that exist within a Station
.
The only relationships in the store are a many-one from Station
to Stop
(each station can have multiple groupings of directions in it, whereas each Stop
can only belong to one station.)
I'm getting four main intermittent errors with my current code being related crashes (EXC_Bad Access, for example), heap corruption relating to "malloc_error_break", NSCFSet being mutated whilst being enumerated, and "CoreData: error: SQLCore dispatchRequest: exception handling request: <NSSQLSaveChangesRequestContext: 0x6000037006c0> , Objects should not be both modified and additional with userInfo of (null)" when I try to save the context.
Here is my current code that fetches the station data and performs a batch insert into Core Data.
static public func refreshData(for context: NSManagedObjectContext) async throws {
let url = URL(string: "https://data.cityofchicago.org/resource/8pix-ypme.json")!
let (data, _) = try await urlSession.data(from: url)
let decoder = JSONDecoder()
var stops = try decoder.decode([IntermediateStop].self, from: data)
var stations = stops
let relationships = stops
let stopsRequest = NSBatchInsertRequest(entity: Stop.entity(), managedObjectHandler: { managedObject in
guard !stops.isEmpty else {
return true
}
let stop = managedObject as! Stop
stop.update(from: stops.removeFirst())
return false
})
let stationsRequest = NSBatchInsertRequest(entity: Station.entity(), managedObjectHandler: { managedObject in
guard !stations.isEmpty else {
return true
}
let station = managedObject as! Station
station.update(using: stations.removeFirst())
return false
})
guard (try context.execute(stopsRequest) as! NSBatchInsertResult).result as! Bool else {
throw StationInitializationError.unsuccessfulInsertion
}
guard (try context.execute(stationsRequest) as! NSBatchInsertResult).result as! Bool else {
throw StationInitializationError.unsuccessfulInsertion
}
context.refreshAllObjects()
for stop in relationships {
let stationRequest: NSFetchRequest<Station> = Station.fetchRequest()
stationRequest.predicate = NSPredicate(format: "id = %@", argumentArray: [stop.stationid])
let stopRequest: NSFetchRequest<Stop> = Stop.fetchRequest()
stopRequest.predicate = NSPredicate(format: "id = %@", argumentArray: [stop.stopid])
let stationResults = try context.fetch(stationRequest)
let stopResults = try context.fetch(stopRequest)
stationResults.first!.addToStops(stopResults.first!)
}
try context.save()
}
The issues were occurring due to bad multithreading: Make sure that you are performing the code that you intend on the correct queue.
static public func refreshData(for context: NSManagedObjectContext) async throws {
let relationships = try await StationFetcher().fetch(ignoresLowDataMode: false)
var stops = relationships
var stations = relationships
try await context.perform {
let stopsRequest = NSBatchInsertRequest(entity: Stop.entity(), managedObjectHandler: { managedObject in
guard !stops.isEmpty else {
return true
}
let stop = managedObject as! Stop
stop.update(from: stops.removeFirst())
return false
})
let stationsRequest = NSBatchInsertRequest(entity: Station.entity(), managedObjectHandler: { managedObject in
guard !stations.isEmpty else {
return true
}
let station = managedObject as! Station
station.update(using: stations.removeFirst())
return false
})
guard (try context.execute(stopsRequest) as! NSBatchInsertResult).result as! Bool else {
throw StationInitializationError.unsuccessfulInsertion
}
guard (try context.execute(stationsRequest) as! NSBatchInsertResult).result as! Bool else {
throw StationInitializationError.unsuccessfulInsertion
}
context.refreshAllObjects()
for stop in relationships {
let stationRequest: NSFetchRequest<Station> = Station.fetchRequest()
stationRequest.predicate = NSPredicate(format: "id = %@", argumentArray: [stop.stationid])
let stopRequest: NSFetchRequest<Stop> = Stop.fetchRequest()
stopRequest.predicate = NSPredicate(format: "id = %@", argumentArray: [stop.stopid])
let stationResults = try context.fetch(stationRequest)
let stopResults = try context.fetch(stopRequest)
stationResults.first!.addToStops(stopResults.first!)
}
if context.hasChanges {
try context.save()
}
}
}