iOS: How do I know if a property is KVO-compliant? iOS: How do I know if a property is KVO-compliant? ios ios

iOS: How do I know if a property is KVO-compliant?


Short answer: No.

Long answer: Nothing in UIKit is guaranteed to be KVO-compliant. If you happen to find that KVO-ing a property works, be grateful, it's unintentional. Also: be wary. It could very well break in the future.

If you find that this is something you need, please file an enhancement request.


About your actual code, it's inherently flawed. Do NOT attempt to add a "rootViewController" setter to UIWindow this way. It will break when you compile your code on iOS 4 but someone runs it on an iOS 5 device. Because you compiled using the 4.x SDK, the #if statements will evaluate to true, meaning your category method smasher will be included in the binary. However, when you run it on an iOS 5 device, you're now going to get a method conflict because two methods on UIWindow will have the same method signature, and there's no guarantee as to which one will be used.

Don't screw with the frameworks like this. If you have to have this, use a subclass. THIS IS WHY SUBCLASSING EXISTS.


Your subclass would look something like this:

@interface CustomWindow : UIWindow@property (nonatomic, retain) UIViewController *rootViewController;@end@implementation CustomWindow : UIWindowstatic BOOL UIWindowHasRootViewController = NO;@dynamic rootViewController;- (void)_findRootViewControllerMethod {  static dispatch_once_t predicate;  dispatch_once(&predicate, ^{    IMP uiwindowMethod = [UIWindow instanceMethodForSelector:@selector(setRootViewController:)];    IMP customWindowMethod = [CustomWindow instanceMethodForSelector:@selector(setRootViewController:)];    UIWindowHasRootViewController = (uiwindowMethod != NULL && uiwindowMethod != customWindowMethod);  });}- (UIViewController *)rootViewController {  [self _findRootViewControllerMethod];  if (UIWindowHasRootViewController) {    // this will be a compile error unless you forward declare the property    // i'll leave as an exercise to the reader ;)    return [super rootViewController];  }  // return the one here on your subclass}- (void)setRootViewController:(UIViewController *)rootViewController {  [self _findRootViewControllerMethod];  if (UIWindowHasRootViewController) {    // this will be a compile error unless you forward declare the property    // i'll leave as an exercise to the reader ;)    [super setRootViewController:rootViewController];  } else {    // set the one here on your subclass  }}

Caveat Implementor: I typed this in a browser window


Based on @David DeLong's solution, this is what I came up with, and it works beautifully.

Basically, I made a category on UIWindow. And in +load, I (run-time) check whether [UIWindow instancesRespondToSelector:@selector(rootViewController)]. If not, I use class_addMethod() to dynamically add the getter & setter methods for rootViewController. Also, I use objc_getAssociatedObject and objc_setAssociatedObject to get & set the rootViewController as an instance variable of UIWindow.

// UIWindow+Additions.h@interface UIWindow (Additions)@end// UIWindow+Additions.m#import "UIWindow+Additions.h"#include <objc/runtime.h>@implementation UIWindow (Additions)#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_4_0// Add rootViewController getter & setter.static UIViewController *rootViewControllerKey;UIViewController *rootViewController3(id self, SEL _cmd);void setRootViewController3(id self, SEL _cmd, UIViewController *newRootViewController);UIViewController *rootViewController3(id self, SEL _cmd) {    return (UIViewController *)objc_getAssociatedObject(self, &rootViewControllerKey);}void setRootViewController3(id self, SEL _cmd, UIViewController *newRootViewController) {    UIViewController *rootViewController = [self performSelector:@selector(rootViewController)];    if (newRootViewController != rootViewController) {        // Remove old views before adding the new one.        for (UIView *subview in [self subviews]) {            [subview removeFromSuperview];        }        objc_setAssociatedObject(self, &rootViewControllerKey, newRootViewController,                                 OBJC_ASSOCIATION_RETAIN_NONATOMIC);        [self addSubview:newRootViewController.view];    }}+ (void)load {    if (![UIWindow instancesRespondToSelector:@selector(rootViewController)]) {        class_addMethod([self class], @selector(rootViewController),                        (IMP)rootViewController3, "@@:");        class_addMethod([self class], @selector(setRootViewController:),                        (IMP)setRootViewController3, "v@:@");    }}#endif@end


Here's a solution using Associative References to define an instance variable with a category. But, it doesn't work cause, according to @Dave DeLong, I must use a run-time (not compile-time) check for this.

// UIWindow+Additions.h@interface UIWindow (Addtions)#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_4_0@property (retain, nonatomic) UIViewController *rootViewController;#endif@end// UIWindow+Additions.m#import "UIWindow+Additions.h"#include <objc/runtime.h>@implementation UIWindow (Additions)#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_4_0@dynamic rootViewController;static UIViewController *rootViewControllerKey;- (UIViewController *)rootViewController {    return (UIViewController *)objc_getAssociatedObject(self, &rootViewControllerKey);}- (void)setRootViewController:(UIViewController *)newRootViewController {    UIViewController *rootViewController = self.rootViewController;    if (newRootViewController != rootViewController) {        // Remove old views before adding the new one.        for (UIView *subview in [self subviews]) {            [subview removeFromSuperview];        }        [rootViewController release];        objc_setAssociatedObject(self, &rootViewControllerKey, newRootViewController,                                 OBJC_ASSOCIATION_RETAIN_NONATOMIC);        [rootViewController retain];        [self addSubview:rootViewController.view];    }}#endif@end