When I fetch the entities after a batch insert in the main queue's managed object context to update my SwiftUI list, I notice there are always duplicates (to be precise, literal doubling of each entity).
I perform a batch insert of the files the user imports through the "import files" method in the Manager class. Then, I fetch them into the managed object context of the main queue to update my SwiftUI list.
class DataController {
static let shared = DataController()
func createNewBackgroundContext() -> NSManagedObjectContext {
let taskContext = container.newBackgroundContext()
taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return taskContext
}
}
class Manager {
func importFiles(from urls: [URL]) async {
let importContext = DataController.shared.createNewBackgroundContext()
var objects: [[String: Any]] = []
for url in urls {
if url.startAccessingSecurityScopedResource() {
let metadata = getMetadata(url: url)
let audioFileName = url.deletingPathExtension().lastPathComponent
let fileExtension = url.pathExtension
let artist = metadata?.value(forKey: "artist") as? String ?? "Unknown Artist"
let album = metadata?.value(forKey: "album") as? String ?? "Unknown Album"
let genre = metadata?.value(forKey: "genre") as? String ?? "Unknown Genre"
let composer = metadata?.value(forKey: "composer") as? String ?? "Unknown Composer"
let year = metadata?.value(forKey: "year") as? String ?? "Unknown Release Year"
let importDate = Date()
let artwork = audioFileName + ".\(fileExtension)"
let trackNumber = metadata?.value(forKey: "trackNumber") as? Int ?? -1
let properties: [String: Any] = [
"name": audioFileName,
"artist": artist,
"album": album,
"genre": genre,
"year": year,
"composer": composer,
"importDate": importDate,
"trackNumber": trackNumber,
"artwork": artwork,
"fileExtension": fileExtension,
"favourite": false
]
objects.append(properties)
//Save the file to disk
save(url: url, with: audioFileName + ".\(fileExtension)")
}
url.stopAccessingSecurityScopedResource()
}
//objects array is confirmed to have the right number of items to be inserted
await importContext.perform { [weak self] in
guard let self else { return }
let batchInsertRequest = NSBatchInsertRequest(entity: AudioFile.entity(), objects: objects)
batchInsertRequest.resultType = .objectIDs
let result = try? importContext.execute(batchInsertRequest) as? NSBatchInsertResult
let objs = result?.result as? [NSManagedObjectID] ?? []
//The number of ids is always double here for some reason...
let changes: [AnyHashable: Any] = [NSInsertedObjectIDsKey: objs]
// Merge changes
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes, into: [moc])
}
await MainActor.run {
SongsListViewModel.shared.fetchAudioFiles()
}
}
}
class SongsListViewModel: ObservableObject {
static let shared = AllSongsListViewModel()
@Published var audioFiles = [AudioFile]()
private let moc = DataController.shared.container.viewContext
func fetchAudioFiles() {
let fetchRequest = AudioFile.fetchRequest()
if let audioFiles = try? moc.fetch(fetchRequest) {
self.audioFiles = audioFiles
}
}
}
I have found the source of the duplicate entries. If anyone cares to explain why "shouldMigrateStoreAutomatically" should be set to true to prevent a NSBatchInsertRequest from inserting a copy for each entity, that would be great.
let description = NSPersistentStoreDescription()
//I have set this property of the description to false all along. Apparently this must be set to true to avoid duplicates when performing a NSBatchInsertRequest
//description.shouldMigrateStoreAutomatically = false
description.shouldMigrateStoreAutomatically = true
container.persistentStoreDescriptions = [description]
There were zero information regarding this on stack overflow or any mention regarding this in the Apple Documentation.