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:
- App loads object context from Core Data
- Notification Manager checks if app is authorized for notification
- If so, the closure calls a method to add a reminder
- within addReminder(), it calls getEvents(...) (see below)
- getEvents raises the EXC_BAD_ACCESS error when cycling through each Contact and trying to access the "events" for each contact (an NSManagedObject). I tried to make changes to isolate any issues associated with the contact or events, but these variables seem to be filled with reasonable data (e.g., the contact may have a proper name or the contact's events list may be empty)
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..
CodePudding user response:
Add adress sanitizer to your scheme, and run application on simulator. Hopofully sanitizer will show you more precisely where in your code you have data race.
- Click on your scheme (next to simulator) -> Edit scheme
- Select diagnostics and check "address sanitizer"
- run your application on simulator
CodePudding user response:
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.

