views:

263

answers:

3

Hi, I have been building a core data application to administrate some data and I've been stumped by a bug that indicates my objects aren't KVO compliant. However, I haven't modified the default KVO compliance of the NSManagedObject, and now I'm left scratching my head.

The area of the application that the bug is affecting is that of creating some categories and organising some items that will live inside those categories. The items may exist in multiple categories and the items AND categories view order can be set by the user. Simple stuff.

To better visualise this, the key pieces of the core data model I have set-up are displayed below. I've simplified the naming and attributes of the entities somewhat:

Category <--------->> CategoryItem <<---------> Item
--------              ------------              --------
name                  viewPosition              name
viewPosition                                    description

This model is connected to two ArrayControllers in interface builder, one for categories and one for categoryitems. The categoryitems content set is set to the category array controller via a selection.categoryItems binding. These array controllers feed two table views. The content of the category items table is bound to the CategoryItem A.C controller via arrangedObjects.item.name.

This all works perfectly and I'm dragging and dropping and ordering both Items and Categories to my hearts desire.

Except for one strange case.

Whenever I create a category with exactly one item, then save, if I then re-open the document I am presented with the following error:

Cannot remove an observer <NSTableBinder 0x1627f670> for the key path "item.name" from <NSManagedObject 0x16273380>, most likely because the value for the key "item" has changed without an appropriate KVO notification being sent. Check the KVO-compliance of the NSManagedObject class.

After that the interface seems to fall apart and the application becomes unusable. I've searched the web and all I can uncover is that this points to KVC non-compliance. But I'm using standard, barely modified, Apple classes here.

The bug ISN'T raised when I bind to the viewPosition of the CategoryItem only. i.e. via arrangedObjects.viewPosition instead of arrangedObjects.item.name. It's as if the relationship between categoryitem and item isn't ready at the point the table initially observes (and only if there is one item).

Has anyone else encountered this? Can anyone think of a possible workaround?

A: 

Cannot remove an observer <NSTableBinder 0x1627f670> for the key path "item.name" from <NSManagedObject 0x16273380>, most likely because the value for the key "item" has changed without an appropriate KVO notification being sent. Check the KVO-compliance of the NSManagedObject class.

This is, indeed, the most likely explanation. You changed the value without posting a KVO notification.

You're probably doing something like this:

[item release];
item = [newItem retain];

This is wrong. Direct instance variable assignment does not post a KVO notification, so this statement does not tell other objects about the change.

You need to go through your accessors, instead. One way is an explicit accessor message:

[self setItem:newItem];

The other way is property assignment:

self.item = newItem;

They are both equivalent and give the exact same results, which include KVO notifications.

Note that both of the correct solutions do not include a retain or copy message. That's the job of your setter accessor (whether you synthesize it, let Core Data create it dynamically, or write it yourself).

The main exception to the always-use-accessors rule is that you should always do direct instance variable assignment (with the appropriate release and retain/copy messages) in init methods and the dealloc method. Otherwise, you're sending messages to a half-inited/-deallocked object.

Peter Hosey
Hi, thanks for your response, but as stated in my post, I am actually using the dynamically created setter/accessors of core data. This has worked fine at all other times, except in the case of re-opening a document with exactly one item (not zero) in a category.
Tricky
A: 

After much head-scratching and feet-stamping I have finally solved this problem. Woohoo!

The cause of this issue actually turned out to be an instance of NSNumberFormatter I had buried in one of my table views. I should have paid more attention to a console message I was getting from Interface Builder.

23/06/2009 15:05:08 ibtool[2324] -[NSConcreteAttributedString initWithString:] called with
nil string argument. This has undefined behavior and will raise an exception in post-
Leopard linked apps. This warning is displayed only once.

What this actually turned out to mean was that my NSNumberFormatter didn't have it's Zero Symbol and Nil Symbol set (The default configuration). Setting these both to zero removed the above error and then as if by magic I was once again KVO compliant.

I worked out that this may be causing the above console message from the following post: http://www.cocoabuilder.com/archive/message/cocoa/2009/5/28/237646

The message did worry me, but as it didn't seem to affect my application and was generated by 'ibtool' (i.e. not my application), I chose to ignore it initially. Mainly because I had no idea of where I would find the culprit that raised the warning. It wasn't until I googled the console message I was able to solve it. I guess I still have much to learn in the world of Cocoa debugging!

What I still find bizarre is that it only affected re-opened documents with exactly one item in a category...

Tricky
This, unfortunately, turned out NOT to be the culprit. It seems that setting my array controller to auto rearrange was the problem. I'm still looking for a fix...
Tricky
A: 

This turned out to be a bug. Here's the report, with the reason and workaround.

NSArrayController 'Auto rearrange content' raises KVO exception for NSManagedObjects

Summary: When 'Auto Rearrange Content' is checked on an NSArrayController that is managing the data of a core data entity, an exception, 'Cannot remove an observer for the key path "x"', is raised.

From my investigation it seems as though that when 'auto rearrange content' is checked on an NSArrayController, KVO compliance is not completely achieved. For the most part, it works perfectly but there is one specific case that I have found that breaks this compliance.

It's tough to articulate, but it seems to occur when trying to access a relationship (i.e arrangedObjects.person.name) of an entity managed by an NSArrayController with auto rearrange contents checked. In addition to this, it only seems to occur after re-opening a document and when that controller is managing a set of exactly one item...

It's probably easier if you view the example project and follow the below steps...

Steps to reproduce: 1) Open the project attached 2) Open the MyDocument.xib 3) Check that 'Auto Rearrange Content' is checked for the 'Activity Persons' NSArrayController 4) Build and go 5) Add one Activity by clicking the plus below the left column 6) Add one Activity Person (no more, no less) by clicking the plus below the right hand column 7) Save the document 8) Re-open the document

Expected results: Document is reopened and user is able to add and remove activities and persons at will with a responsive interface.

Actual results: UI behaves unpredictably as soon as the right hand column attempts to display the Activity Persons of an Activity with exactly one Activity Person. Renders application unusable.

Notes: I created a test project to locate this bug, the code and model should be extremely self-explanatory, but feel free to contact me with any questions.

This problem can be worked around by unchecking Auto Rearrange Content and calling arrangeObjects on NSArrayController manually.

Tricky