[英]iOS: How do I know if a property is KVO-compliant?
在Key-Value Observing Programming Guide中, Registering for Key-Value Observing部分说:“Apple 提供的框架中的典型属性只有在记录时才符合 KVO。” 但是,我没有在文档中找到任何符合 KVO 的属性。 你能指点我一些吗?
具体来说,我想知道UIWindow
的@property(nonatomic,retain) UIViewController *rootViewController
是否符合 KVO。 原因是我将rootViewController
属性添加到UIWindow
for iOS < 4 并且想知道我是否应该使其符合 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 属性有效,请不胜感激,这是无意的。 还有:小心。 它很可能在未来破裂。
如果您发现这是您需要的,请提交增强请求。
关于您的实际代码,它本质上是有缺陷的。 不要尝试以这种方式将“rootViewController”设置器添加到UIWindow
。 当您在 iOS 4 上编译代码但有人在 iOS 5 设备上运行它时,它将中断。 因为您使用 4.x SDK 进行编译,所以#if
语句将评估为 true,这意味着您的类别方法粉碎器将包含在二进制文件中。 但是,当您在 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
}
}
警告实施者:我在浏览器中输入了这个 window
基于@David DeLong 的解决方案,这是我想出的,而且效果很好。
基本上,我在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
这是一个使用关联引用来定义具有类别的实例变量的解决方案。 但是,它不起作用,因为根据@Dave DeLong 的说法,我必须为此使用运行时(而不是编译时)检查。
// 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
,在该 UIWindow3 曾经访问过rootViewController
。 (注意:我认为@David DeLong 的解决方案可能不需要进行这些额外的更改,而只是始终使用CustomWindow
而不是UIWindow
。)因此,这比我可以(仅适用于 iOS < 4)更烦人,只需将rootViewController
添加到UIWindow
通过一个类别。 我可能会考虑使用关联引用(仅适用于 iOS < 4)的类别来执行此操作,因为我认为这看起来像是最 eloquent 解决方案,并且可能是学习和使用工具箱的好技术。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.