Checking for nil Core Data property results in EXC_BAD_ACCESS
Asked Answered
C

3

6

I have a simple NSManagedObject subclass:

@objc class MyModel: NSManagedObject {
    @NSManaged var myProperty: String
}

However, the following code:

var model = NSEntityDescription.insertNewObjectForEntityForName("MyModel", inManagedObjectContext: managedObjectContext) as MyModel

assert(model != nil) // passes

if model.myProperty != nil { //crashes
    println("not nil")
}

crashes at if model.myProperty != nil with a EXC_BAD_ACCESS. Why is this happening? This only started happening with Beta 5, and worked properly with Beta 4.

The above class was automatically generated using Xcode, so they did not add a ? to the end of the property. However, manually adding a ? to the end of the property does resolve the issue (@NSManaged var myProperty: String?).

My question is, why doesn't Xcode automatically add the question mark to make it optional if it is marked as such in the schema, and why was this not an issue in previous betas?

Conic answered 5/8, 2014 at 14:42 Comment(4)
Why would myProperty ever be nil if it is not an optional?Nealey
@Jeff It is optional, at least is marked as such in the schema. However, the above class was automatically generated using Xcode.Conic
Ah ok, my mistake. Interesting reading your update that adding the question mark fixed it. Perhaps flag an issue with the bug reporter.Nealey
Looks like a bug to me. I'd file a radar on itFought
K
7

To make it work you should do 2 things :

1) in the NSManagedObject subclass add ? to made the property optional

@objc class MyModel: NSManagedObject {
    @NSManaged var myProperty: String? // <-- add ? here
}

2) in your implementation as suggested in the previous answer

if let aProperty = model.myProperty? {
    // do something with aProperty
}

Note that if you forgot to add ? in the NSManagedObject subclass you we have compilation error.

Kep answered 8/9, 2014 at 11:48 Comment(1)
The auto-generated NSManagedObject Swift class files fail to annotate optional properties with ? in the version of Xcode I was using. Watch out for that.Olaolaf
V
0

Actually, the pattern Apple suggests in its Swift Programming Language guide is

if let aProperty = model.myProperty? {
    // do something with aProperty
}
Virgel answered 6/8, 2014 at 9:1 Comment(0)
G
0

It is very important to track where and how the properties are accessed. In my case my core data stack is build with one parent NSManagedObjectContext and temporary child NSManagedObjectContext, which are doing the writing and deleting on a background thread, with the guidance of this article.

In my case the problem was with one (unusually) complex operation involving few nested for loops and async calls to the back-end. Because of the way our CMS system works we can not fetch the data normally, so we will have to resolve to the nested loops. So, imagine a lobby page with different tabs of games. Each tab has sections of games. In order to populate it I am:

  1. Fetching the tabs
  2. I save them in Core Data with a block, which underneath is creating a new instance of NSManagedObjectContext on its own background thread (check the linked article above)
  3. After they are saved I am iterating over them and with their id I am making an asynchronous request using Swift 5.5's Async/Await functions to fetch all game groups and games
  4. I save the results in Core Data

It is so obvious that it is easy to overlook, but after spending a day and a half in debugging I figured it out. When I store the tab entities with the temporary NSManagedObject I am then creating a Task and I use the tab's identifier property to fetch the games. Which unfortunately leads to crash because. But doing so I am introducing another thread in the picture and the app crashes.

Task.detached {
    // `tab`'s MOC is created in a different thread and accessing it's properties can lead to a crash
    let tabId = tab.tabId 
}

Now, in order to fix this the safest way I managed to find is:

let tabId = tab.tabId
Task.detached {
    ...fetch the games using the `tabId` value
    if let threadSafeTab = managedObjectContext.object(with: tab.objectID) as? GamesTab {
    // Now the entity is synced properly and we can safely access it's properties
    threadSafeTab.addToGamesSections(section)
    }
}

For reference check Apple's documentation on object(with:)

I hope my answer is helpful :)

Grimaldi answered 11/5 at 18:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.