Walkthrough of a recent Core Data bug
As I mentioned in the March recap I’ve starting to migrate ProfitTrain over to Core Data. Lots of things have been going well but there was a problem I ran into. That being, I wasn’t seeing inverse relationships update properly.
I would tell a client its business was X, and then ask X for a list of his clients and not see the client. Calling -processPendingChanges on the context had no effect.
I spent days on this issue, posting a StackOverflow question, building sample projects; all to no avail. Most object creation in my app was in one place (the model controller) and I was fine with manually setting up both ends of the relationship manually and moving on to work on other things for the time being. That same day I decided to move on from this first bug I experienced my second.
I was refactoring my source list code and noticed a problem. I couldn’t NSLog an instance of PTClient. Doing so resulted in no logging from that NSLog statement and no logging from any NSLog statement henceforth. The app didn’t crash or hang. I could even use breakpoints to step through things — just no logging was happening. Really strange.
Later in the week I came back to the problem and with the help of a few IRC friends attempted to narrow down what was happening. For a while it felt like maybe I had a corrupt model file but considering I was getting no complier warnings or runtime errors and the app functioned well as long as I didn’t attempt to log out this instance it didn’t make much sense.
I eventually came to attempt to create and NSLog each entity type I had in my model file. The only one that didn’t work was PTClient. I then discovered if I removed the clients<->>business relationship between Business and Client entities things logged fine. If I re-added the relationship it would break. If I renamed the relationship on the Client from business to ofBusiness things worked fine. Getting close.
Someone asked me if I was overriding business in someway. PTClient is a pretty small class, didn’t see anything directly for business but I have this one method called isBusiness.
Why did I had a method called isBusiness in my PTClient subclass? We’ll in a previous version of my app I just used a simple CBCompany class and a little toggle to mark the company as a business or a client. Moving the model over to Core Data I’ve since created a PTCompany abstract entity and let PTBusiness and PTClient inherit from it. The isBusiness method was carried over as a compatibility method as there were iterators out in my code that would ask onto a group of companies, isBusiness.
If I removed the isBusiness method from PTClient everything worked, even the inverse relationship bug from earlier. Wow.
So the big questions here is why. Why is it that if I have a relationship called business and a method called isBusiness everything fucks up like this? The short answer is Key Value Coding. To quote the documentation:
For properties that are an attribute or a to-one relationship, this requires that your class:
- Implement a method named
-<key>,-is<Key>, or have an instance variableor _<key>.- If the property is mutable, then it should also implement
-set<Key>:.- Your implementation of the
-set<Key>:method should not perform validation.- Your class should implement
-validate<Key>:error:if validation is appropriate for the key.
I’m fine in that Core Data lives a world of Cocoa and KVC, and there are certain names I should avoid (like naming an attribute of an Entity “description” or what not). What I would like however is some kind of warning that the method I’m implementing or the name I have chosen is a bad idea. The lack of feedback from the system (no complier errors were generated, no exceptions were thrown, no runtime errors logged to console) was really frustrating for me.
I’ve post a bug in radar (#7848360) and have a sample project if you want to see the carnage for yourself.
Here’s to hopping the rest of my Core Data conversion goes smoothly. :)
Posted on: April 9, 2010 – 8:32 PM

3 Comments
Wow. Glad you got that worked out. I’ve been too busy to look at it further on StackOverflow but if I had I’m sure I wouldn’t have thought of that. Very good to know the final solution and initial cause too.
This post was a life saver. Development was going great, until all of a sudden everything broke and I couldn’t figure out what was happening. The symptoms were the same as yours (NSLog() crashed, inverse relationship not updated). As it turned out, you should not call your relationship ‘deleted’, because it collides with NSManagedObject::isDeleted(). Thanks so much!
Glad to hear it was so helpful Andrzej.
Post a Comment | Comment RSS feed