How to debug KVO

In my program, I use KVO manually to observe changes in object property values. I get an EXC_BAD_ACCESS signal in the following line of code inside a custom setter:

 [self willChangeValueForKey:@"mykey"]; 

The strange thing is that this happens when the factory method calls the custom setter, and there shouldn't be any observers around it. I do not know how to debug this situation.

Update:. A list of all registered observers is observationInfo . It turned out that the object was actually specified, pointing to an invalid address. However, I do not know at all how he got there.

Update 2: Apparently, the same callback of the object and method can be registered several times for this object, which leads to identical entries in the observed object observationInfo . When you unregister, only one of these entries is deleted. This behavior is a little contrary to intuition (and it is certainly a mistake in my program to add several entries at all), but it does not explain how false observers can mysteriously appear in freshly allocated objects (unless caching / reuse exists that I don’t I know).

Modified question: How can I find out WHERE and WHEN an object is registered as an observer?

Update 3: Specific code example.

ContentObj is a class that has a dictionary as a property called mykey . It redefines:

 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey { BOOL automatic = NO; if ([theKey isEqualToString:@"mykey"]) { automatic = NO; } else { automatic=[super automaticallyNotifiesObserversForKey:theKey]; } return automatic; } 

A pair of properties has getters and setters as follows:

 - (CGFloat)value { return [[[self mykey] objectForKey:@"value"] floatValue]; } - (void)setValue:(CGFloat)aValue { [self willChangeValueForKey:@"mykey"]; [[self mykey] setObject:[NSNumber numberWithFloat:aValue] forKey:@"value"]; [self didChangeValueForKey:@"mykey"]; } 

The container class has the contents property of the NSMutableArray class, which contains instances of the ContentObj class. It has several methods that manually process the registration:

 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey { BOOL automatic = NO; if ([theKey isEqualToString:@"contents"]) { automatic = NO; } else { automatic=[super automaticallyNotifiesObserversForKey:theKey]; } return automatic; } - (void)observeContent:(ContentObj *)cObj { [cObj addObserver:self forKeyPath:@"mykey" options:0 context:NULL]; } - (void)removeObserveContent:(ContentObj *)cObj { [cObj removeObserver:self forKeyPath:@"mykey"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (([keyPath isEqualToString:@"mykey"]) && ([object isKindOfClass:[ContentObj class]])) { [self willChangeValueForKey:@"contents"]; [self didChangeValueForKey:@"contents"]; } } 

There are several methods in a container class that modify contents . They look like this:

 - (void)addContent:(ContentObj *)cObj { [self willChangeValueForKey:@"contents"]; [self observeDatum:cObj]; [[self contents] addObject:cObj]; [self didChangeValueForKey:@"contents"]; } 

And a couple more others that provide similar functionality to the array. They all work by adding / removing themselves as observers. Obviously, everything that leads to numerous registrations is a mistake and may contain somewhere hidden in these methods.

My question discusses strategies for debugging this kind of situation. As an alternative, please feel free to suggest an alternative strategy for implementing this type of notification / observer template.

Update 4:. I found a bug using a mixture of breakpoints, NSLog s, code reviews and sweating. I did not use context in KVO, although this is certainly another useful suggestion. This was an incorrect double registration, which for reasons beyond my understanding led to the observed behavior.

An implementation involving [self willChange...]; [self didChange...] [self willChange...]; [self didChange...] , works as described (on iOS 5), although it is far from beautiful. The problem is that since NSArray not KVO-compatible, it is impossible to talk about changes in its contents. I also thought about the notifications offered by Mike Ash, but I decided to go with KVO, as it looked like a more Cocoa like mechanism for working. This may not be the best solution ...

+4
source share
2 answers

In response to your modified question, try overriding addObserver: forKeyPath: options: context: in your custom class and set a breakpoint on it. Alternatively, you can simply set the symbolic breakpoint to -[NSObject addObserver:forKeyPath:options:context:] , but that will probably hit hard.

+4
source

Yes, calling -addObserver: will lead to two registrations twice. The Foo class and some subclass of Foo, Bar can both (legally) register for the same notification, but with different contexts (always include context, always check the context in -observeValueForKeyPath and always call super in -observeValueForKeyPath ).

This means that the instance of Bar will be registered twice, and rightly so.

However, you almost certainly do not want to register the same object / key path / context several times by accident, but as @wbyoung says overriding -addObserver:forKeyPath:options:context: should help you make sure that this does not happen. If you need to track observers / keypath / context in an array and make sure that they are unique.

Mike Ash has some interesting thoughts and code on his blog about using contexts . He is right about the fact that he is broken, but in practice KVO is quite applicable.

That is, when you use it to accomplish something, it means todo. It used to be that you absolutely could not do something like this.

 [self willChangeValueForKey:@"contents"]; [self didChangeValueForKey:@"contents"]; 

because it is a lie. The value of "content" when you call -willChange.. should be a different value when you call -didChange.. The KVO mechanism will call -valueForKey:@"contents" in -willChangeValueForKey and -didChangeValueForKey to verify that the value has changed. This will obviously not work with the array, no matter how you modify the contents that you still have the same object. Now I do not know if this is still the case (the web search did not appear on anything), but note that -willChangeValueForKey, -didChangeValueForKey are not the correct way to handle the kvo collection manual. For this, Apple offers alternative methods: -

 – willChange:valuesAtIndexes:forKey: – didChange:valuesAtIndexes:forKey: – willChangeValueForKey:withSetMutation:usingObjects: – didChangeValueForKey:withSetMutation:usingObjects: 

Maybe not everything is true that the value should change, but if so, your circuit will not work.

What I would do is one notice of changes to your collection. And another notice for changing items in this collection. those. the moment you try to trigger notifications for @ "content", when instead you can have @ "content" and @ "propertiesOfContents". You will need to observe two key paths, but you can use automatic kvo instead of manually triggering notifications. (Using automatic kvo ensures that the correct versions of -willChange.. -didChange.. )

For an automatic kvo array, take a look at (no NSArrayController): - Key value ratio in Cocoa

Then, every time an item is added to the collection, observe the properties you need (as you are doing now), and when they change the value for self.propertiesOfContents . (OK, as I read that back it doesn’t necessarily sound less hacky than your decision, but I still think that it can behave better).

+6
source

Source: https://habr.com/ru/post/1380745/


All Articles