IOS: How do I know if the KVO property matches?

In the Key Values ​​Monitoring Guide, the Register to Monitor Key Values section says: Typically, the properties in Apple-supplied frameworks are only KVO-compatible if they are documented as such. "But I did not find any properties in the documentation that are documented as KVO-compatible Could you point me some?

In particular, I would like to know if @property(nonatomic,retain) UIViewController *rootViewController of UIWindow KVO-compatible. The reason is because I am adding the rootViewController property to UIWindow for iOS <4 and want to know if I should make this KVO-compatible.

 @interface UIWindow (Additions) #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_4_0 @property (nonatomic, retain) UIViewController *rootViewController; #endif; @end @implementation UIWindow (Additions) #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_4_0 @dynamic rootViewController; - (void)setRootViewController:(UIViewController *)newRootViewController { if (newRootViewController != _rootViewController) { // Remove old views before adding the new one. for (UIView *subview in [self subviews]) { [subview removeFromSuperview]; } [_rootViewController release]; _rootViewController = newRootViewController; [_rootViewController retain]; [self addSubview:_rootViewController.view]; } } #endif @end 
+15
ios objective-c cocoa-touch ios4 key-value-observing
Jul 07 2018-11-11T00:
source share
4 answers

Short answer: No.

Long answer: nothing at UIKit guarantees KVO compliance. If you find that KVO-ing property is working, be grateful, this is unintentional. Also: be careful. In the future, this could be torn.

If you find that this is what you need, write a request for improvement .




About your real code, it is inherently flawed. DO NOT try to add the "rootViewController" installer to the UIWindow this way. It will crash when you compile your code on iOS 4, but someone runs it on an iOS 5 device. Since you compiled using the SDK 4.x, the #if statements will evaluate to true, which means your category method smashher will be included in the binary. However, when you run it on your iOS 5 device, you will now get a method conflict because the two methods on UIWindow will have the same method signature, and there is no guarantee which one will be used.

Do not twist with such frames. If you need to do this, use a subclass. THIS WHY EXISTS THIS.




Your subclass will look something like this:

 @interface CustomWindow : UIWindow @property (nonatomic, retain) UIViewController *rootViewController; @end @implementation CustomWindow : UIWindow static 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

+16
Jul 07 2018-11-11T00:
source share

Based on the @David DeLong solution , this is what I came up with and it works great.

Basically, I made a category on UIWindow . And in +load , I (the runtime) checks if there is [UIWindow instancesRespondToSelector:@selector(rootViewController)] . If not, I use class_addMethod() to dynamically add the getter and setter methods for rootViewController . In addition, I use objc_getAssociatedObject and objc_setAssociatedObject to get and set 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 
0
Jul 08 '11 at 20:26
source share

It uses a solution using associative links to define an instance variable with a category . But, this does not work, according to @Dave DeLong, I have to use runtime (not compile time) 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 
-one
Jul 07 '11 at 20:20
source share

Based on @David DeLong feedback, I went with a simple subclass, like this:

 // UIWindow3.h #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_4_0 @interface UIWindow3 : UIWindow { } @property (nonatomic, retain) UIViewController *rootViewController; @end #endif // UIWindow3.m #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_4_0 #import "UIWindow3.h" @implementation UIWindow3 @synthesize rootViewController; - (void)setRootViewController:(UIViewController *)newRootViewController { if (newRootViewController != rootViewController) { // Remove old views before adding the new one. for (UIView *subview in [self subviews]) { [subview removeFromSuperview]; } [rootViewController release]; rootViewController = newRootViewController; [rootViewController retain]; [self addSubview:rootViewController.view]; } } @end #endif 

However, it also required passing through existing code and using conditional compilation to drop UIWindow to UIWindow3 , where it ever accessed rootViewController . (Note: I think the @David DeLong solution may not require these additional changes, but just always use CustomWindow instead of UIWindow .) This is more annoying than if I could (for iOS <4 only) just add rootViewController in UIWindow through a category. I can learn this with category using Associative links (iOS <4 only), because I think it seems like this will be the most eloquent solution and may be a good technique to learn and use in the toolbar.

-2
Jul 07 '11 at 18:23
source share



All Articles