Eisfair
Eisfair

Reputation: 49

Swift CoreData Background NSManagedObject Fault

i have the following problem:

I create a NSManagedObject

func createRecord(participation: Participation, start: NSDate, course: Course?) -> Record {

    let newRecord = NSEntityDescription.insertNewObjectForEntityForName("Record", inManagedObjectContext: bmoc) as! Record

    newRecord.setValue(participation, forKey: "participation");
    newRecord.setValue(start, forKey: "start");
    newRecord.setValue(course, forKey: "course");
    newRecord.setValue(0, forKey: "finished");
    newRecord.setValue(0, forKey: "experience");
    newRecord.setValue(false, forKey: "synced");

    var err : NSError?
    bmoc.save(&err)
    if(err != nil) {
        NSLog(err!.description)
    }

    return newRecord;

Then i use this object in a controller with a NSTimer that calls this

println(record!.start); labelTimer.setTime(NSDate().timeIntervalSinceDate(record!.start))   

The Controller has these two methods defined to start and stop the timer. In AppDelegate those methods are called and work like expected.

func applicationWillResignActive(application: UIApplication) {
    println("applicationWillResignActive")
    trackingDelegate?.stopTimer()
}

func applicationDidBecomeActive(application: UIApplication) {
    println("applicationDidBecomeActive")
    trackingDelegate?.startTimer()
}

The basic idea is to have a timer the users starts with a button which calls startTimer() aswell. When the user closes the app with the home button, StopTimer() is called and works without any problems. When the app didBecomeActive, i get the following error at println(record!.start)

THREAD 1: EXC_BAD_ACCESS (code=1, address = 0x0)

Does anyone have an idea why? The Object is persistent. If i check the database with an sqlite-viewer, i see it.. When i try to access the other two objects participation and course in the controller directly with something like course.name, it works, record.course.name does not. Only the Record-Object seems to have a problem.

Runtime value of Record-Object:

Printing description of self.record:
(StudyTracker.Record?) record = 0x00007ff7a2ee3ea0 {
  CoreData.NSManagedObject = {
    NSManagedObject = {
      NSObject = {
        isa = StudyTracker.Record_Record_
      }
      _cd_rc = 4
      _cd_stateFlags = 40667072
      _cd_rawData = nil
      _cd_entity = 0x00007ff7a2e74dd0
      _cd_managedObjectContext = 0x00007ff7a2d2fbb0
      _cd_objectID = 0xd00000000158000a
      _cd_extraFlags = 104
      _cd_observationInfo = nil
      _cd_snapshots = 0x0000000000000000
      _cd_lockingInfo = 0
      _cd_queueReference = 0x00007ff7a2d4a490
    }
  }
}

Code for CoreData is the default one:

lazy var applicationDocumentsDirectory: NSURL = {
    // The directory the application uses to store the Core Data store file. This code uses a directory named "de.unikassel.StudyTracker" in the application's documents Application Support directory.
    let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
    return urls[urls.count-1] as! NSURL
    }()

lazy var managedObjectModel: NSManagedObjectModel = {
    // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
    let modelURL = NSBundle.mainBundle().URLForResource("StudyTracker", withExtension: "momd")!
    return NSManagedObjectModel(contentsOfURL: modelURL)!
    }()

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
    // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
    // Create the coordinator and store
    var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("StudyTracker.sqlite")
    var error: NSError? = nil
    var failureReason = "There was an error creating or loading the application's saved data."
    if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil, error: &error) == nil {
        coordinator = nil
        // Report any error we got.
        var dict = [String: AnyObject]()
        dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
        dict[NSLocalizedFailureReasonErrorKey] = failureReason
        dict[NSUnderlyingErrorKey] = error
        error = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
        // Replace this with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog("Unresolved error \(error), \(error!.userInfo)")
        abort()
    }

    return coordinator
    }()

lazy var managedObjectContext: NSManagedObjectContext? = {
    // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
    let coordinator = self.persistentStoreCoordinator
    if coordinator == nil {
        return nil
    }
    var managedObjectContext = NSManagedObjectContext()
    managedObjectContext.persistentStoreCoordinator = coordinator
    return managedObjectContext
    }()

Best regards and thanks in advance, Christian

Upvotes: 0

Views: 444

Answers (2)

Eisfair
Eisfair

Reputation: 49

Basicly using this context with concurrencyType MainQueueConcurrencyType when setting up coredata did also the trick, especially when working with a wiget later on.

var managedObjectContext = NSManagedObjectContext(concurrencyType:.MainQueueConcurrencyType)

Duncan's advice worked, but was somehow redundant when i had to change the context type due to sharing data with a widget.

Thanks for the help!

Upvotes: 1

Duncan Groenewald
Duncan Groenewald

Reputation: 8988

The timer will be calling your code on a different thread to the one you used to create the managedObject, so you will need to make sure you call the println(!record.start) from the same thread, which is, presumably, the main thread. Something like this:

NSOperationQueue.mainQueue().addOperationWithBlock {
                    println(!record.start)
                }

or

dispatch_async(dispatch_get_main_queue(), {
                println(!record.start)
            })

Upvotes: 0

Related Questions