• Open Menu Close Menu
  • Apple
  • Shopping Bag
  • Apple
  • Mac
  • iPad
  • iPhone
  • Watch
  • TV
  • Music
  • Support
  • Search apple.com
  • Shopping Bag

Lists

Open Menu Close Menu
  • Terms and Conditions
  • Lists hosted on this site
  • Email the Postmaster
  • Tips for posting to public mailing lists
Re: KVO and the observeValueForKeyPath bottleneck
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: KVO and the observeValueForKeyPath bottleneck


  • Subject: Re: KVO and the observeValueForKeyPath bottleneck
  • From: Jakob Olesen <email@hidden>
  • Date: Tue, 18 Jul 2006 16:06:32 +0200


On 18/07/2006, at 3.00, Chris Kane wrote:

You and Matt Neuburg and Jim Correia (privately) have all now identified all the parts of the problem and the solution.

Well, not really. Note that removeObserver:forKeyPath: does not take a context argument. It just removes the last context added. This means it is not safe to remove yourself as an observer until dealloc (or didTurnIntoFault). You also need to trust that sub- and superclasses will do the same.


Otherwise you have to use a helper object.

The solution is that the context pointer must be used to provide a globally unique value that you can recognize in your class. If you recognize the value, this notification is for you, otherwise you pass the method call up to super and return. You have to use a value that the other classes in the hierarchy won't (or can't) use.

This is of course the way to go, but in the process we have unanswered Matt's original question, "How are you guys doing the dispatch thing?"


...and it is really painful to waste a perfectly good void pointer on multiple personality resolution. :-)

How about this:

static SEL dispatch[10]; // make it big enough or die violently

static void* sel2ctx(SEL s)
{
    int i;
    for (i=0; i<sizeof(dispatch)/sizeof(dispatch[0]); i++) {
        if (!dispatch[i]) dispatch[i]=s;
        if (s==dispatch[i]) return dispatch+i;
    }
    abort(); // I told you...
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id) object change:(NSDictionary *)change context:(void *)context
{
SEL *entry = (SEL*)context;
if (entry>=dispatch && entry<dispatch+sizeof(dispatch)/sizeof (dispatch[0])) {
[self performSelector:*entry withObject:...];
}
else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}


fgrep -c @selector MyClass.m would be an upper bound on the necessary size of the dispatch table
Oh, and you would have to pre-fill the table if you are multithreaded. Call sel2ctx in +initialize or use an initializer list:


sed -n 's/^.*\(@selector([^)]*)\).*$/    \1,/p' MyClass.m | sort -u

This is not just a matter of performance, also convenience. You already have to keep your addObserver and removeObserver calls in sync. Adding custom dispatch code to observeValueForKeyPath makes it even harder to maintain.

Also, keyPath+object is not always so great for deciding what to do. Imagine observing the same keyPath on objects in two collections. You have to check the collections. Now imagine the same object present in both collections. You get two identical callbacks.

So what is the right thing to do? It depends.

For a heavy-weight class (few instances, lots of state) use a helper object.
If you need removeObserver outside dealloc/didTurnIntoFault, use a helper object.
If you need to observe self, use a helper object.
For a light-weight class (lots of instances, little state) use a unique context and custom dispatch if it can be kept simple, otherwise a dispatch table.
If you are working alone and writing a non-reusable NSObject subclass, just use a selector directly. (Don't complicate things until you have to).


You can't include something *inside* the context, because you can't safely look through the pointer at anything in particular, as the pointer might not be pointing to the data structure you know about. You have to generate a unique pointer value and act based on that. An == comparison is dirt cheap if you can do it.

I have seen Windows code ask the virtual memory manager if an address is readable, then check for a magic value. Messy and not 100% safe, but possible.


If you assume that @"" string constants are NOT globally uniqued in a binary, but only to a given compilation unit, then ThisClassUniqueObservationContext could be a constant string:

static NSString * ThisClassUniqueObservationContext = @"MyContext";

CFSTR() does global uniqueing, at least in the open-sourced version, but there is also a mystery __builtin___CFStringMakeConstantString implementation.


Note that if this were "const", that could thwart things, since integer constants might also be uniqued and shared by the linker.

Really? I thought only C++ allowed that. C99? After all you are passing a pointer to your constant outside the compilation unit.


But whatever way you dice it, "yuck". Usually I've not wanted the context pointer for anything and just wanted to use NULL ... but for this potential problem.

I agree.

NSNotificationCenter causes less trouble by taking a selector instead of a void pointer. It still has the problem with removing observers, though. removeObserver:name:object: does not take a selector argument, so it is the same mess if you try to unregister before dealloc.



_______________________________________________
Do not post admin requests to the list. They will be ignored.
Cocoa-dev mailing list      (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden


References: 
 >Re: KVO and the observeValueForKeyPath bottleneck (From: Matt Neuburg <email@hidden>)
 >Re: KVO and the observeValueForKeyPath bottleneck (From: Chris Kane <email@hidden>)
 >Re: KVO and the observeValueForKeyPath bottleneck (From: Jakob Olesen <email@hidden>)
 >Re: KVO and the observeValueForKeyPath bottleneck (From: Chris Kane <email@hidden>)

  • Prev by Date: Re: Writing application without Interface Builder
  • Next by Date: Re: Symmetric Distributed Objects
  • Previous by thread: Re: KVO and the observeValueForKeyPath bottleneck
  • Next by thread: Re: KVO and the observeValueForKeyPath bottleneck
  • Index(es):
    • Date
    • Thread