How a Core Data Attribute's Name Can Lead to Crashes
TL;DR: You can’t name a Core Data attribute new*
in Objective-C or Swift
because of how automatic reference counting interacts with manual reference
counting. If you do, you’ll crash unreliably when your Core Data stack is
deallocated.
I’ve spent the better part of a day trying to fix this hard to track down bug in
my Core Data stack that was causing a crash. The crash was introduced when I
added a new entity to my object model. The NSManagedObject
subclass looked a
little like this:
public final class RenamedThing: NSManagedObject {
@NSManaged private(set) public var newName: String
@NSManaged private(set) public var oldName: String
@NSManaged public var markedForLocalDeletionDate: Date?
}
Any tests that accessed RenamedThing
’s newName
attribute would crash when
the NSPersistentContainer
was set to nil
in the test’s tearDown()
method. The crash message looked like:
xctest(..) malloc: *** error for object ..: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
After eliminating any possibility of this being a bug related to Core Data’s
concurrency model, I broke out Xcode’s memory graph debugger and then NSZombie
to see if I could find anything. This felt like an odd thing to do since this is
Swift and so automatic reference counting (ARC) is active and I can’t make any
manual calls to release
anywhere. In the end, neither the memory graph
debugger nor NSZombie
were of any help.
At this point I was starting to think that this might be a bug in Core Data. To check I wasn’t doing something crazy stupid with ARC though, I checked through the documentation on ARC. Eventually, I came across the Transitioning to ARC release notes and found this lovely tidbit under the “new” rules section:
You cannot give an accessor a name that begins with new. This in turn means that you can’t, for example, declare a property whose name begins with new unless you specify a different getter[.]
I renamed the newName
attribute to something else and, of course, my tests
stopped crashing. This is both my favourite and least favourite type of bug—a
bug that takes hours to figure out but just a second to fix.
I was surprised to find that the transitioning to ARC guide is still relevant today, even in Swift. I think this means that Core Data is still using manual memory management (at least in parts) which is a little surprising given how long ARC has been available and how complicated the memory management must be for Core Data. Still, “if it ain’t broke then don’t fix it” is probably a really good rule to stick by for something as complex as Core Data.