EXC_BAD_ACCESS code=EXC_ARM_DA_ALIGN

892 Views Asked by At

I've tested my code with 3 x iPhone 5's, a 5s, 6, 6s, and 7.

I'm getting the above error on all of the iPhone 5 devices only. No idea what is going on here but perhaps the fact that the 5's are 32bit devices may be a clue?

I'm calling the following method from a viewcontroller class

func startRecording() {
    disableControls()

    CoreDataStack.shared.performForegroundTask { (context) in
            let sessionInfo = SessionInfo(context: context)
            sessionInfo.startTime = Date().timeIntervalSince1970
            sessionInfo.userId = self.config.userId
            sessionInfo.devicePosition = self.config.devicePosition.rawValue
            sessionInfo.deviceType = self.config.deviceType.rawValue
            sessionInfo.deviceNumber = self.config.deviceNumber
            sessionInfo.deviceSide = self.config.deviceSide.rawValue

            do {
                    try context.obtainPermanentIDs(for: [sessionInfo])
            } catch {
                    print("Error obtaining permanent ID for session info record")
                    return
            }

            CoreDataStack.shared.saveViewContextAndWait()

            DispatchQueue.main.async {
                    guard sessionInfo.objectID.isTemporaryID == false else {
                            print("ObjectID is temporary")
                            return
                    }

                    self.recording = true
                    self.statusLabel.text = "Recording..."
                    self.recordManager.start(sessionUID: sessionInfo.uid)
            }
    }
}

The config variable is a simple struct:

struct Configuration {
    var userId: String = "Unknown"
    var deviceType: DeviceType = .phone // enum: String
    var deviceSide: DeviceSide = .notApplicable // enum: String
    var deviceNumber: Int16 = 1 
    var devicePosition: DevicePosition = .waist  // enum: String
}

The CoreDataStack is here:

final class CoreDataStack {
    static let shared = CoreDataStack()
    private init() {}

    var errorHandler: (Error) -> Void = { error in
            log.error("\(error), \(error._userInfo)")
    }

    private struct constants {
            static let persistentStoreName = "Model"
    }

    private lazy var persistentContainer: NSPersistentContainer = {
            let container = NSPersistentContainer(name: constants.persistentStoreName)
            container.loadPersistentStores(completionHandler: { [weak self] (storeDescription, error) in
                    if let error = error {
                            self?.errorHandler(error)
                    }
            })
            return container
    }()

    lazy var viewContext: NSManagedObjectContext = {
            self.persistentContainer.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
            self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
            try! self.persistentContainer.viewContext.setQueryGenerationFrom(.current)
            return self.persistentContainer.viewContext
    }()

    private lazy var backgroundContext: NSManagedObjectContext = {
            let context = self.persistentContainer.newBackgroundContext()
            context.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump
            return context
    }()

    func performForegroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) {
            self.viewContext.performAndWait {
                    block(self.viewContext)
            }
    }

    func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) {
            backgroundContext.perform {
                    block(self.backgroundContext)
            }
    }

    func saveBackgroundContext() {
            viewContext.performAndWait {
                    do {
                            if self.viewContext.hasChanges {
                                    try self.viewContext.save()
                            }
                    } catch {
                            self.errorHandler(error)
                    }

                    self.backgroundContext.perform {
                            do {
                                    if self.backgroundContext.hasChanges {
                                            try self.backgroundContext.save()
                                            self.backgroundContext.refreshAllObjects()
                                    }
                            } catch {
                                    self.errorHandler(error)
                            }
                    }
            }
    }

    func saveViewContext() {
            viewContext.perform {
                    if self.viewContext.hasChanges {
                            do {
                                    try self.viewContext.save()
                            } catch {
                                    self.errorHandler(error)
                            }
                    }
            }
    }

    func saveViewContextAndWait() {
            viewContext.performAndWait {
                    if self.viewContext.hasChanges {
                            do {
                                    try self.viewContext.save()
                            } catch {
                                    self.errorHandler(error)
                            }
                    }
            }
    }
}

The code is bombing out on the following line in the startRecording method:

try context.obtainPermanentIDs(for: [sessionInfo])

Edit:

I've created a stripped down test application consisting of only the CoreDataStack and a model with one entity with one attribute of type string. I'm still getting the same error on 3x iPhone 5's only. 5s, 6, 6s, 7 all work fine.

It would imply the problem lies with the CoreDataStack perhaps?

Github repo here

3

There are 3 best solutions below

0
On BEST ANSWER

A couple of people have asked if I solved this, so here is what I did. This is not really a solution but more of a workaround. It may not be appropriate for everyone but worked for me.

All I did was remove the line

try! self.persistentContainer.viewContext.setQueryGenerationFrom(.current)

from the CoreDataStack and the issue went away...

8
On

I'll take a couple of guesses based on a short look at your code.

The old-school developer in me is drawn to the Configuration member var deviceNumber: Int16 = 1. Incorrect alignment settings or old compilers might cause the next item to have the wrong alignment. You could try making it the last item in the struct.

Another item which stands out is the assignment sessionInfo.deviceNumber = self.config.deviceNumber. This looks to be assigning an Int16 to an NSNumber which may be a problem. (I am assuming that SessionInfo is an NSManagedObject based on the overall code and its initializer taking a context argument. That would mean all numeric members are NSNumbers.)

Try changing the line to

sessionInfo.deviceNumber = NSNumber(int:self.config.deviceNumber)

I'm not yet familiar with the iOS 10 additions to Core Data, but from what I'm reading the viewContext is read-only. But viewContext is being used to create a new object in this code. Is the Xcode console showing any more information when you get to this point in the debugger?

4
On

NSPersistentContainer is a setup for a core data stack with clear expectation on how to use it. You are misusing it. viewContext is readonly so remove performForegroundTask, saveViewContext and saveViewContextAndWait. Don't change the viewContext's mergePolicy. In performBackgroundTask just use NSPersistentContainer's performBackgroundTask which has the same method signature. Don't use newBackgroundContext. If you are using NSPersistentContainer your CoreDataStack should be doing next to nothing.

If you want a different custom stack unlike what NSPersistentContainer sets up then don't use NSPersistentContainer - just create your own stack. But the setup that you are trying to write has major problems. Writing from both background contexts and the viewContext has major problems when writes happen at the same time. mergePolicy can help that but you can end up missing information that you thought you saved. You are much better off learning to use the stack that NSPersistentContainer sets up.