在Key-Value Observing Programming Guide中,Registering for Key-Value Observing部分说“通常,Apple提供的框架中的属性只有符合KVO标准才会被记录。”但是,我没有在文档中找到任何记录为KVO兼容的属性。你能指点一下吗?
具体来说,我想知道@property(nonatomic,retain) UIViewController *rootViewController
的UIWindow
是否符合KVO标准。原因是我将rootViewController
属性添加到iOS <4的UIWindow
,并想知道我是否应该使其符合KVO标准。
@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
简答:不。
答案很长:UIKit中的任何内容都不能保证符合KVO标准。如果您碰巧发现KVO-ing房产有效,请感激,这是无意的。另外:要小心。它可能在未来很好地打破。
如果您发现这是您需要的,请file an enhancement request。
关于你的实际代码,它本质上是有缺陷的。不要试图以这种方式向UIWindow
添加“rootViewController”setter。当你在iOS 4上编译代码时它会破坏,但有人在iOS 5设备上运行它。因为您使用4.x SDK编译,所以#if
语句将评估为true,这意味着您的类别方法smasher将包含在二进制文件中。但是,当您在iOS 5设备上运行它时,您现在将遇到方法冲突,因为UIWindow
上的两个方法将具有相同的方法签名,并且无法保证将使用哪个方法。
不要像这样使用框架。如果必须这样,请使用子类。这就是为什么要归类的原因。
您的子类看起来像这样:
@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
}
}
警告执行者:我在浏览器窗口中输入了这个
基于@David DeLong's solution,这是我想出来的,它的工作非常精彩。
基本上,我在UIWindow
上做了一个类别。在+load
,我(运行时)检查是否[UIWindow instancesRespondToSelector:@selector(rootViewController)]
。如果没有,我使用class_addMethod()
动态添加rootViewController
的getter和setter方法。另外,我使用objc_getAssociatedObject
和objc_setAssociatedObject
来获取和设置rootViewController
作为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
这是使用Associative References to define an instance variable with a category的解决方案。但是,根据@Dave DeLong的说法,它不起作用,我必须使用run-time (not compile-time) check。
// 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
基于@David DeLong的反馈,我选择了一个简单的子类,如下所示:
// 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
但是,这也需要通过现有代码并使用条件编译将UIWindow
强制转换为UIWindow3
,而rootViewController
正在被访问。 (注意:我认为@David DeLong的解决方案可能不需要进行这些额外的更改,而只是总是使用CustomWindow
而不是UIWindow
。)因此,这比我能够(仅适用于iOS <4)更加烦人,只需将rootViewController
添加到UIWindow
通过一个类别。我可能会考虑使用category using Associative References(仅适用于iOS <4),因为我觉得它看起来像是最有说服力的解决方案,可能是一个很好的技术,可以在工具箱中学习和使用。