views:

525

answers:

4

According to Apple and numerous examples I've seen, there is no problem using KVO/KVC to observer yourself. Also according to those same sources, it's not a problem setting this up by using addObserver:forKeypath:options:context: in an object's init method, a la:

- (id)init
{
    self = [super init];
    if (self) {
    [self addObserver:self
               forKeyPath:@"selected"
                  options:NSKeyValueObservingOptionNew
                  context:NULL];
    }
    return self;
}

Unfortunately for some reason, my observer method does not get called when I do it there. If I move the addObserver call to another method and then invoke that method in the calling method:

MyObject *newObj = [[MyObject alloc] init];
[newObj setupObservers];

Then all is fine. This is a subclass of NSImageView, so it's not like there's any 'awakeFromNib'-type alternative here... I'm really scratching my head here and I'm sure I'm missing something obvious - like a rule about things which will cause KVO on self to not work in init methods, but I haven't found anything in the docs which would give me any hints here.

What do I not know?

A: 

I'm not sure whether there is such restriction or not, but even if you don't have a awakeFromNib you can create one by adding setupObservers to the run-loop in your init method:

[[NSRunLoop currentRunLoop] 
  performSelector:@selector(setupObservers) 
  target:self 
  argument:nil 
  order:1 
  modes:NSDefaultRunLoopMode];
Aviad Ben Dov
Sure, I have little doubt that that would work, but doesn't that seem like a hack? Also, doing things that way now doesn't really tell me anything about why it wouldn't work in the first place - which means that there's still information that I'm not aware of about what may be poor programming habits on my part. I'd really rather have things work the way they're supposed to because I'm doing it right... You know?
Chronor
performSelector:afterDelay and its variants are powerful methods that are easily abused. This is an abuse. What happens if selected changes in between now and when the selector eventually fires? What about if we do something that causes us to stop observing - woops haven't started. It's best to avoid using this method to "fix" problems. There are great usages for it, this just isn't one of them.
Jon Hess
@Jon, @Chronor: This is definitely a hack. It just might be a working hack, and sometimes that's good enough... If you can "afford" to call `setupObservers` yourself, then it's obviously better.
Aviad Ben Dov
A: 

This is a subclass of NSImageView, so it's not like there's any 'awakeFromNib'-type alternative here...

I don't understand this point. Are you creating this object in a NIB or not? If a NIB is creating this object, then it will call -awakeFromNib. The first thing you should establish (using NSLog()) is whether your -init is actually running. When nothing happens, it usually means code did not run.

Rob Napier
Perhaps I should have been more clear. Yes, it is a subclass of NSImageView, but it is one that I am instantiating in code as part of another action. I then add it as a subview to an existing in-window custom view. Therefore no awakeFromNib. Sorry for the confusion.
Chronor
+2  A: 

The problem is probably that -init is not invoked in your case, -initWithCoder: is.

Every Cocoa class has a set of init methods called its "designated initializers". Each object, as its being instantiated, is guaranteed to go through one and only one of the designated initializers of each class in its inheritance tree.

If you're subclassing a class and have initialization to do, you must override all of the designated initializers of the superclass.

The designed initializers of NSImageView are -initWithCoder: and initWithFrame:. Override those two, not init.

Also, "selected" is too generic of a method to add to a subclass of a NSImageView. What if Cocoa decided to add a -selected method to NSControl or NSImageView? The semantics probably wouldn't match yours, and badness would ensue. See if you can come up with a more specific description of your use than "selected", and if not, use a prefix.
Somebody find me an idiot stick with which I can hit myself. How could I miss the fact that init isn't the designated initializer for that class? Of course that was it and making the appropriate change remedied the problem. Thanks for reminding me!And yes, I agree that "selected" is probably not the best property name to use. I'll come up with something else. Thanks...
Chronor
+2  A: 

As for the context pointer, the preferred way is:

static void *MyPrivateObservationContext = (void*)@"MyPrivateObservationContext"; // we assume MyPrivateObservationContext is a unique name, I use something of the form ClassNamePropertyObservationContext

then

-[obj add....... context:&MyPrivateObservationContext];

Then in

-(void)observeValueForKeyPath:....context:c;  
{  
    if (c == &MyPrivateObservationContext) {  
        // do work  
    } else {  
        [super observeValueForKeyPath:...];  
    }  
}
Jonathan Dann