1

I have the following taskGroup, using the async/await concurrency in Swift 5.5, where I iterate over meters, fetching onboardingMeter from Core Data. This works ok in around 4 out of 5 times, but then crashes the iOS app. I am testing this with deleting the app from the iOS device, and run the onboarding from the start. Sometimes I can run this with no problems 4 or 5 or 6 times in a row, then it suddenly crashes with the following error:

2021-09-05 09:11:01.095537+0200 Curro[12328:1169419] [error] error: Serious application error.  Exception was caught during Core Data change processing.  This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification.  -[__NSCFSet addObject:]: attempt to insert nil with userInfo (null)
CoreData: error: Serious application error.  Exception was caught during Core Data change processing.  This is usually a bug within an observer of NSManagedObjectContextObjectsDidChangeNotification.  -[__NSCFSet addObject:]: attempt to insert nil with userInfo (null)
2021-09-05 09:11:01.095954+0200 Curro[12328:1169419] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet addObject:]: attempt to insert nil'
*** First throw call stack:
(0x1857b105c 0x19dc63f64 0x1858ba538 0x1858c5df8 0x1857c68bc 0x18ce313d8 0x18cd13314 0x18cd140cc 0x105616700 0x105626a90 0x185769ce4 0x185723ebc 0x1857373c8 0x1a0edd38c 0x1880dae9c 0x187e58864 0x18d36da2c 0x18d29ab70 0x18d27bf2c 0x1048bba04 0x1048bbab0 0x10551da24)
libc++abi: terminating with uncaught exception of type NSException

This is the code where it fails, the let onboardingMeter = await OnboardingMeter.fromMeterDict is never returned, I don't see the second print statement when it fails.

Is this an error in the Swift beta, or have I made any mistake here? I will try again when a new iOS 15 beta arrives, and when the official release of iOS 15 comes out.

await withTaskGroup(of: OnboardingMeter.self) { group in
    for (number, codableAddress) in meters {
        group.addTask {
            print("The following statement sometimes fails to return an onboarding meter, resulting in a crash")
            let onboardingMeter = await OnboardingMeter.fromMeterDict(number, address: codableAddress, in: moc)
            print("onboardingMeter:", onboardingMeter)
            return onboardingMeter
        }
    }
    for await meter in group {
        onboardingMeters.append(meter)
    }
}

The OnboardingMeter.fromMeterDict function:

extension OnboardingMeter {
    static func fromMeterDict(_ number: String, address: CodableAddress, in moc: NSManagedObjectContext) async -> OnboardingMeter {
        let me: OnboardingMeter = await moc.findOrCreateInstance(of: self, predicate: NSPredicate(format: "number == \(number)"))
        let address = await Address.createOrUpdate(from: address, in: moc)
        await moc.perform {
            me.address = address
            me.number = number
        }

        return me
    }
}

and findOrCreateInstance:

    func findOrCreateInstance<T: NSManagedObject>(of type: T.Type, predicate: NSPredicate?) async -> T {
        if let found = await self.findFirstInstance(of: type, predicate: predicate) {
            return found
        } else {
            return T(context: self)
        }
    }
Ivan C Myrvold
  • 680
  • 7
  • 23

1 Answers1

0

Although I'd need to see more code to confirm this, I believe that you're returning a managed object that is already registered to a managed object context. It is only valid to refer to such registered objects within the closure of a perform call. If you need to refer to a managed object between different execution contexts, either make use of the object ID and refetch as needed, or make use of the dictionary representation option of the fetch request:

let request: NSFetchRequest = ...
request.resultType = NSManagedObjectIDResultType // or NSDictionaryResultType
return request.execute()

I'd strongly encourage you to watch this video from WWDC. At the minute 10:39 they're talking exactly about this.

Vladimir
  • 521
  • 5
  • 16