I'm writing a Swift iOS app in XCode 12.5 that lets you take notes on "events" (interactions) with your contacts. After making some changes, launching the app on my phone yielded an EXC_BAD_ACCESS error from a part of the app I haven't touched in months - a notification manager that creates a reminder based on the latest notes you've taken on any of your contacts.
As background, I use CoreData to store the app's data on contacts and notes, using NSManagedObjects to represent Contact objects; each Contact object has 0 to many Event objects. The app was working fine on my iPhone as of a few months ago, and on the simulator as I've been making changes to the code in recent days/weeks. I've tried using the Instruments tool to check for Zombies and Leaks and have come up empty-handed. I've also tried to assess variables on the line of code causing the bad access error and can't find issues.
At a high level, this is the execution sequence leading to the problem:
func getEvents(last90days: Bool = false) -> [Event] {
var events = [Event]()
let keyDate = Date(timeIntervalSinceNow: -90 * 60 * 60 * 24)
for case let event as Event in (self.events ?? []) { // this is the line where the EXC_BAD_ACCESS occurs. Even if I nil-coalesce above this line separately, the problem still persists.
if last90days && event.timestamp != nil {
if event.timestamp == nil {
continue
}
if event.timestamp! < keyDate {
continue
}
}
events.append(event)
}
return events
}
_Error Message:_Thread 4: EXC_BAD_ACCESS (code=1, address=0x1fa9bf0) Stack Trace:
* thread #4, queue = 'com.apple.usernotifications.UNUserNotificationServiceConnection.call-out', stop reason = EXC_BAD_ACCESS (code=1, address=0x1fa9bf0)
frame #0: 0x00000001ace04334 libobjc.A.dylib`object_getMethodImplementation + 48
frame #1: 0x00000001982d7c04 CoreFoundation`_NSIsNSSet + 40
frame #2: 0x00000001981aaf18 CoreFoundation`-[NSMutableSet unionSet:] + 108
frame #3: 0x000000019e3c93b0 CoreData`-[_NSFaultingMutableSet willReadWithContents:] + 636
frame #4: 0x000000019e3e7ff4 CoreData`-[_NSFaultingMutableSet countByEnumeratingWithState:objects:count:] + 48
frame #5: 0x000000019bd12bd0 libswiftFoundation.dylib`Foundation.NSFastEnumerationIterator.next() -> Swift.Optional<Any> + 180
* frame #6: 0x0000000100dbb03c myApp`Contact.getEvents(last90days=false, self=0x0000000281b32f80) at Contact+helpers.swift:48:9
frame #7: 0x0000000100db7bc8 myApp`InteractionAnalyzer.countInteractions(startDate=2022-01-03 04:12:17 UTC, endDate=2022-01-10 04:12:17 UTC, name=nil, onlyIncludeNewSparks=false, excludeNewSparks=false, sparkStartDate=nil, self=0x00000002838feb20) at InteractionAnalyzer.swift:24:48
frame #8: 0x0000000100dfac18 myApp`NotificationManager.getNotificationString(self=0x000000028377a220) at NotificationManager.swift:74:60
frame #9: 0x0000000100dfa368 myApp`NotificationManager.addNotificationRequest(self=0x000000028377a220) at NotificationManager.swift:62:29
frame #10: 0x0000000100df9ab8 myApp`closure #1 in NotificationManager.addReminder(settings=0x0000000281355490, self=0x000000028377a220) at NotificationManager.swift:36:22
frame #11: 0x0000000100df98d4 myApp`thunk for @escaping @callee_guaranteed (@guaranteed UNNotificationSettings) -> () at <compiler-generated>:0
frame #12: 0x000000010146c0b4 libdispatch.dylib`_dispatch_call_block_and_release + 32
frame #13: 0x000000010146dde0 libdispatch.dylib`_dispatch_client_callout + 20
frame #14: 0x0000000101475ef0 libdispatch.dylib`_dispatch_lane_serial_drain + 788
frame #15: 0x0000000101476d48 libdispatch.dylib`_dispatch_lane_invoke + 496
frame #16: 0x0000000101483a50 libdispatch.dylib`_dispatch_workloop_worker_thread + 1600
frame #17: 0x00000001e3f927a4 libsystem_pthread.dylib`_pthread_wqthread + 276
How can I get to the bottom of this cause? I've been crawling many threads on EXC_BAD_ACCESS to no avail and am hoping I'm just missing something dead simple..
The NSFaultingMutableSet in the stack trace pointed to an issue with accessing data from my Core Data store (e.g., Event objects like self.events). My notification manager was operating on a separate thread and creating an unstable situation where Core Data (which I hadn't set up properly for multi-thread access) was being read and modified on the main and secondary threads simultaneously.
I was able to resolve the problem by wrapping the Notification Manager code that accesses Core Data objects in a DispatchQueue.main.async {...}
block. There are other ways to set up Core Data objects for access from multiple threads (e.g., Coredata - Multithreading best way), but this was the simplest solution given multi-thread access isn't a priority for what I'm trying to do.