Lukasz
Lukasz

Reputation: 2317

EXC_BAD_ACCESS error when inserting NSManagedObject

I get an EXC_BAD_ACCESS error when inserting an NSManagedObject at the superclass init call. The error doesn't always occur, so my guess is that it has something to do with threading, however I'm very new to iOS development so I might be completely wrong.

In the code below I mark the line where the error occurs with a comment.

import Foundation
import CoreData

@objc(Measurement)
public class Measurement: NSManagedObject {
    convenience init(sensorId: Int32, fromDatetime: Int64, pm10: Float, pm25: Float, airQualityIndex: Float32, pollutionLevel: Int16,
                     latitude: Double, longitude: Double, source: String, windDeg: Float32, windSpeed: Float32,
                     context: NSManagedObjectContext) {
        if let ent = NSEntityDescription.entity(forEntityName: "Measurement", in: context) {
            self.init(entity: ent, insertInto: context) // Thread 10: EXC_BAD_ACCESS (code=1, address=0xfffffffc)
            self.fromDatetime = fromDatetime
            self.pm10 = pm10
            // omitted value asignments
        } else {
            fatalError("Unable to find entity name Measurement")
        }
    }
}

The inserting happens after a call to an api inside a for loop.

    let task = session.dataTask(with: request) { (data, response, error) in
        // if an error occurs, print it and re-enable the UI
        func displayError(_ error: String) {
            print(error)
        }

        /* GUARD: Was there an error? */
        guard (error == nil) else {
            displayError("There was an error with your request: \(String(describing: error))")
            return
        }

        /* GUARD: Did we get a successful 2XX response? */
        guard let statusCode = (response as? HTTPURLResponse)?.statusCode, statusCode >= 200 && statusCode <= 299 else {
            displayError("Your request returned a status code other than 2xx!")
            return
        }

        /* GUARD: Was there any data returned? */
        guard let data = data else {
            displayError("No data was returned by the request!")
            return
        }

        // parse the data
        let parsedResult: [[String:AnyObject]]!
        do {
            parsedResult = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [[String:AnyObject]]
        } catch {
            displayError("Could not parse the data as JSON: '\(data)'")
            return
        }
        let delegate = UIApplication.shared.delegate as! AppDelegate
        let stack = delegate.stack
        var counter = 0
        for measurement in parsedResult {

            if counter == 0 {
                print(measurement)
            }

            guard let sensorId = measurement[Constants.MeasurementModelKeys.sensorId] as? Int32 else {
                print("Cannot find key 'sensor_id' in \(measurement)")
                continue
            }

            // other guarded assignments ...

            _ = Measurement(sensorId: sensorId, fromDatetime: fromDatetime, pm10: pm10, pm25: pm25,
                            airQualityIndex: airQualityIndex, pollutionLevel: pollutionLevel, latitude: latitude, longitude: longitude,
                            source: source, windDeg: windDeg, windSpeed: windSpeed,
                            context: stack.context) // << this is where the insertion happens

            counter += 1
        }
        print("counter counted: \(counter) elements")

    }

    // start the task!
    task.resume()
}

Any help is much appreciated (including tips on how to track down the exact cause of the error).

Upvotes: 0

Views: 141

Answers (1)

deadbeef
deadbeef

Reputation: 5563

Core Data is indeed very touchy about threading. Inserting new objects in a context should be performed on that context queue.

Try this :

stack.context.perform {
    _ = Measurement(sensorId: sensorId, fromDatetime: fromDatetime, pm10: pm10, pm25: pm25,
                    airQualityIndex: airQualityIndex, pollutionLevel: pollutionLevel, latitude: latitude, longitude: longitude,
                    source: source, windDeg: windDeg, windSpeed: windSpeed,
                    context: stack.context)
}

Note that this is now an asynchronous call. Make sure this doesn't break your code somewhere else.

Upvotes: 1

Related Questions