CoreData, Faulting, and KVO
CoreData, Faulting, and KVO
- Subject: CoreData, Faulting, and KVO
- From: "Jim Thomason" <email@hidden>
- Date: Tue, 20 Mar 2007 08:57:53 -0500
In my earlier message from a few days ago (titled Releasing an
NSManagedObjectContext), I said that I was having problems getting rid
of a temporary NSManagedObjectContext, and when I tried to, I'd crash
with an exception:
2007-03-18 18:21:11.535 Debtinator[4711] An uncaught exception was raised
2007-03-18 18:21:11.541 Debtinator[4711] The NSManagedObject with
ID:0x382ff0 <x-coredata://2E18AFC3-F2C9-4A2E-9AA7-D46BA377FF3D/Frobnoz/p113>
has been invalidated.
2007-03-18 18:21:11.548 Debtinator[4711] *** Uncaught exception:
<NSObjectInaccessibleException> The NSManagedObject with ID:0x382ff0
<x-coredata://2E18AFC3-F2C9-4A2E-9AA7-D46BA377FF3D/Frobnoz/p113> has
been invalidated.
I "fixed" this issue by sending my context an autorelease instead of a
release. The exception remained (in fact, I began -always- receiving
the NSManagedObject has been invalidated exception), but I guess it
became a nonfatal warning. So, technically, I could have stopped, but
I'm of the ilk that all warnings are bad and I wanted to get rid of
it.
Here's where my investigation lead me. If there's a better solution,
I'd love to hear it.
For starters, I have a tree like structure in my model:
Account -< LineItem -< Rate
An account has many line items, and a line item has many rates. The
issue was that the Account object needs to pay attention to the values
of its line items, and the line items need to pay attention to their
rates.
Basically, an account alone doesn't have a balance, the account's
balance is the sum of its line items' balances. It doesn't have a
minimum fee, its minimum fee is the sum of its line items' minimum
fees. But -those- don't have minimum fees either, their minimum fees
are the sum of their rate objects' minimum fees.
So I had several observers set up. The account would watch all the
line item balances and update its own balance as appropriate. It'd
watch their minimum fees, and update as appropriate. Likewise, the
line items would watch their rates to know when to update their
minimum fees.
It may sound complicated, but it was really straightforward.
The problem came when the context was released. As best as I can tell,
this is what happened. But, keep in mind, this is me trying to
backfill what's happening to explain a problem, I could be wrong:
1) The account turned into a fault, and, I assume, was deallocated.
2) A line item came along and tried to turn into a fault, as that
happened, it set all of its values to nil, which in turn triggered a
KVO note to the account that its values were changing.
3) This was sent back to the account and brought it back to life.
4) The line item then turned into a fault and deallocated.
5) The reference to the account was now gone, so the system tried to
deallocate it, and tossed out that warning.
I may not have the chain of events -quite- right, but that's how it
seemed to work.
I tried overloading release or dealloc on my line item and rate
objects to release the observers, but it didn't work - those methods
were called too late. I tried implementing didTurnIntoFault, but
again, it was called too late to be of any use. As best as I can tell,
the method that I -really- needed was a "willTurnIntoFault". If I
could've gotten rid of my observers there, I think I would've been
fine.
It was basically a chicken or the egg problem. I didn't (and as far as
I know, couldn't) know what order my objects were being removed. So I
couldn't just tell the account object to remove its observers on the
line items, since those could have been already removed and I'd bring
them back to life as well. I'm a little foggy on the chain of events
here, but regardless - implementing didTurnIntoFault on my Account had
no effect.
Fortunately, I came up with a solution that seems to work - don't use
any observers at all.
Instead of having my line items call
addObserver:forKeyPath:options:context: to add my account as an
observer for the important properties, I instead overrode
willChangeValueForKey: and didChangeValueForKey:. I then checked those
- if I was changing the key that my account cared about, I propogated
up a willChangeValueForKey: or didChangeValueForKey: to that object.
And, ta-da! This works. All of my parent object values are updated at
the appropriate time w/o complaint, and I don't get any warnings when
I get rid of my temporary context.
Of course, this raises the question that I always have - Is this a
hack? Is there some better, more standard way to do it? To me, it
seems like I'd actually want this to work via addObserver:..., and
that it -should- work that way, it just doesn't. So did I miss
something? Or is this just one of those things that needs to be hacked
around for now? Again, overriding (will|did)ChangeValueForKey: does
work just fine.
Cheers,
-Jim....
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden