diff --git a/objectivec/Tests/GPBMessageTests.m b/objectivec/Tests/GPBMessageTests.m index ea224fa6138b..b120a6861c6e 100644 --- a/objectivec/Tests/GPBMessageTests.m +++ b/objectivec/Tests/GPBMessageTests.m @@ -41,6 +41,52 @@ #import "google/protobuf/Unittest.pbobjc.h" #import "google/protobuf/UnittestObjc.pbobjc.h" #import "google/protobuf/UnittestObjcOptions.pbobjc.h" +#import "google/protobuf/UnittestImport.pbobjc.h" + +// Helper class to test KVO. +@interface GPBKVOTestObserver : NSObject { + id observee_; + NSString *keyPath_; +} + +@property (nonatomic) BOOL didObserve; +- (id)initWithObservee:(id)observee keyPath:(NSString *)keyPath; +@end + +@implementation GPBKVOTestObserver + +@synthesize didObserve; + +- (id)initWithObservee:(id)observee keyPath:(NSString *)keyPath { + if (self = [super init]) { + observee_ = [observee retain]; + keyPath_ = [keyPath copy]; + [observee_ addObserver:self forKeyPath:keyPath_ options:0 context:NULL]; + } + return self; +} + +- (void)dealloc { + [observee_ removeObserver:self forKeyPath:keyPath_]; + [observee_ release]; + [keyPath_ release]; + [super dealloc]; +} + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context +{ +#pragma unused(object) +#pragma unused(change) +#pragma unused(context) + if ([keyPath isEqualToString:keyPath_]) { + self.didObserve = YES; + } +} + +@end @interface MessageTests : GPBTestCase @end @@ -431,6 +477,55 @@ - (void)testKVC_SetValue_ForKey { [self assertAllFieldsKVCMatch:message]; } +- (void)testKVOBasic { + TestAllTypes *message = [TestAllTypes message]; + GPBKVOTestObserver *observer = + [[[GPBKVOTestObserver alloc] initWithObservee:message + keyPath:@"optionalString"] + autorelease]; + XCTAssertFalse(observer.didObserve); + message.defaultString = @"Hello"; + XCTAssertFalse(observer.didObserve); + message.optionalString = @"Hello"; + XCTAssertTrue(observer.didObserve); +} + +- (void)testKVOAutocreate { + TestAllTypes *message = [TestAllTypes message]; + GPBKVOTestObserver *autocreateObserver = + [[[GPBKVOTestObserver alloc] initWithObservee:message + keyPath:@"optionalImportMessage"] + autorelease]; + GPBKVOTestObserver *innerFieldObserver = + [[[GPBKVOTestObserver alloc] initWithObservee:message + keyPath:@"optionalImportMessage.d"] + autorelease]; + XCTAssertFalse(autocreateObserver.didObserve); + XCTAssertFalse(innerFieldObserver.didObserve); + + int a = message.optionalImportMessage.d; + XCTAssertEqual(a, 0); + + // Autocreation of fields is not observed by KVO when getting values. + XCTAssertFalse(autocreateObserver.didObserve); + XCTAssertFalse(innerFieldObserver.didObserve); + + message.optionalImportMessage.d = 2; + + // Autocreation of fields is not observed by KVO. + // This is undefined behavior. The library makes no guarantees with regards + // to KVO firing if an autocreation occurs as part of a setter. + // This test exists just to be aware if the behavior changes. + XCTAssertFalse(autocreateObserver.didObserve); + + // Values set inside of an autocreated field are observed. + XCTAssertTrue(innerFieldObserver.didObserve); + + // Explicit setting of a message field is observed. + message.optionalImportMessage = [ImportMessage message]; + XCTAssertTrue(autocreateObserver.didObserve); +} + - (void)testDescription { // No real test, just exercise code TestAllTypes *message = [TestAllTypes message];