Question

I am having some difficulty with key-value-observing logic in conjunction with XCTest (the original code is being retrofitted with test coverage). The logic works fine in the normal (non-test) context, but blows out with an exception every time in the context of a test.

The gist of the logic is this -- I have two classes, call them Service and Helper. Scaffold implementations are:

interface Service : NSObject {
    BOOL svcCallComplete;
}
@end

@implementation Service

- (id) init {
    if ((self=[super init])==nil) {
        return nil;
    }
    return self;
}
@end

interface Helper : NSObject {
}
@end

@implementation Helper

- (id) init {
    if ((self=[super init])==nil) {
        return nil;
    }
    return self;
}
@end

Helper is an observer of an attribute in Service. In the context of my normal runtime logic I do this with a call to a Service instance method addSvcObserver:

Service.m:

- (void) addSvcObserver:(id)observer {
    [self addObserver:observer
           forKeyPath:@"svcCallComplete"
              options:NSKeyValueObservingOptionNew
              context:nil];
}

Helper complies with the KVO observing pattern thus:

Helper.m:

- (void) observeValueForKeyPath:(NSString*)keyPath
                       ofObject:(id)object
                         change:(NSDictionary*)change
                        context:(void*)context {
}

Pretty straight-forward, and I won't go into the logic for monitoring the attribute change as the problem occurs way before that -- if I have a code excerpt like:

Service* service = [[Service alloc] init];
Helper* helper = [[Helper alloc] init]; 

[service addSvcObserver:helper];

there are no problems in the non-test case (i.e., this and associated KVO logic works as expected). However the addSvcObserver call when performed in the context of an XCTest test method produces an immediate access denied exception. I've included an exception "break on all" breakpoint -- the problem seems to be occurring in objc_registerClassPair during the addObserver:forKeyPath:options:context: call. The test target has ARC explicitly disabled as the project for which it is providing test coverage (the target is iOS7) is non-ARC for legacy reasons; this does not seem to cause any problems with other tests.

Thoughts?

Was it helpful?

Solution 2

The cause of this turned out to be that my implementation of the KVO logic was incomplete. According to the guide here, you must override the NSObject implementation of automaticallyNotifiesObserversForKey: when using manual change notification -- I somehow missed that in my initial read of the text. I added the following to my Service class:

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {

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

and all is now correct in the test case. Anyone care to hazard a guess as to why this was not blowing out in the normal (non-test) case?

OTHER TIPS

interface Service : NSObject {
}
@property (nonatomic) BOOL svcCallComplete;

You should declare svcCallComplete as a property.

because The observed class must be key-value observing compliant for the property that you wish to observe

The reason you get the objc_registerClassPair i think maybe because KVO dynamic register a subclass of your Service , but could not find a setter method of svcCallComplete.which the dynamic subclass needs to override that setter method and send notification.

For more detail read this.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top