KVO: Observation of non-autonotifying can send wrong number of -observeValue... notifications
| Originator: | chaos42 | ||
| Number: | rdar://15359322 | Date Originated: | 30-Oct-2013 10:26 PM |
| Status: | Open | Resolved: | |
| Product: | iOS SDK | Product Version: | 6.1/10B141 |
| Classification: | Other Bug | Reproducible: | Always |
Summary:
If you add an observer on a non-autonotifying object twice, call -willChangeValueForKey:, call -removeObserver:forKeyPath: once, change the value and then call -didChangeValueForKey:, the observer receives -observeValueForKeyPath:... messages twice instead of once.
Steps to Reproduce:
1. Compile and run the following code with Xcode 5.0.1, iOS 6.1 Simulator target
@interface Observer : NSObject
@end
@implementation Observer
{
NSMutableDictionary *_observationCounts;
NSMutableSet *_priorNotificationValues;
}
+ (instancetype)observer
{
return [[[self alloc] init] autorelease];
}
- (id)init
{
self = [super init];
if (self != nil)
{
_observationCounts = [NSMutableDictionary dictionary];
_priorNotificationValues = [NSMutableSet set];
}
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSNumber *count = [_observationCounts objectForKey:keyPath];
if (count == nil)
{
count = @(0);
}
[_observationCounts setObject:@([count integerValue] + 1) forKey:keyPath];
if ([change objectForKey:NSKeyValueChangeNotificationIsPriorKey] != nil)
{
[_priorNotificationValues addObject:keyPath];
}
}
- (NSUInteger)observationCountForKeyPath:(NSString *)keyPath
{
NSNumber *count = [_observationCounts objectForKey:keyPath];
if (count == nil)
{
count = @(0);
}
return [count unsignedIntegerValue];
}
- (BOOL)priorObservationMadeForKeyPath:(NSString *)keyPath
{
return [_priorNotificationValues containsObject:keyPath];
}
@end
@interface Observable : NSObject {
int _anInt;
}
@property int anInt;
@property Class someClass;
@property union foo { int a; float b; } someUnion;
@end
@implementation Observable
@synthesize anInt=_anInt;
+ (instancetype)observable
{
return [[[self alloc] init] autorelease];
}
@end
@interface ReallyBadObservable : Observable
@end
@implementation ReallyBadObservable
- (void)setAnInt:(int)anInt
{
_anInt = anInt;
}
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
{
return NO;
}
@end
-(BOOL)testMidCyclePartialUnregister
{
@autoreleasepool
{
ReallyBadObservable *badObservable = [ReallyBadObservable observable];
Observer *observer = [Observer observer];
[badObservable addObserver:observer forKeyPath:@"anInt" options:0 context:NULL];
[badObservable addObserver:observer forKeyPath:@"anInt" options:0 context:NULL];
[badObservable willChangeValueForKey:@"anInt"];
[badObservable setAnInt:50]; //this line and the following line can be rearranged without affecting the result.
[badObservable removeObserver:observer forKeyPath:@"anInt"];
[badObservable didChangeValueForKey:@"anInt"];
testassert([observer observationCountForKeyPath:@"anInt"] == 1);
[badObservable removeObserver:observer forKeyPath:@"anInt"];
return YES;
}
}
Expected Results:
This test should pass.
Actual Results:
This test fails. The call to [observer observationCountForKeyPath:@"anInt"] returns 2.
Version:
6.1 Simulator
Comments
Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!