Home > Software design >  Finding the cause of EXC_BAD_ACCESS Code=1 on startup of Swift iOS App
Finding the cause of EXC_BAD_ACCESS Code=1 on startup of Swift iOS App

Time:01-13

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.

  1. Click on your scheme (next to simulator) -> Edit scheme
  2. Select diagnostics and check "address sanitizer"
  3. run your application on simulator

picture1

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.

  •  Tags:  
  • Related