2011-07-07 21 views
15

Key-Value Observing Programming Guide中,Registering for Key-Value Observing部分指出:“如果Apple等提供的框架中的属性记录为如此,那么它们通常只具有KVO兼容性。”但是,我没有在文档中找到任何记录为KVO兼容的属性。你能指点我一些吗?iOS:如何知道某个媒体资源是否符合KVO标准?

具体而言,我想知道UIWindow@property(nonatomic,retain) UIViewController *rootViewController是否符合KVO。原因是我将rootViewController属性添加到UIWindow对于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 

回答

16

简短的回答:第

龙答:UIKit中没有什么是保证KVO兼容。如果您碰巧发现KVO-property属性有效,请注意,这是无意的。另外:要小心。它可能会在未来破裂。

如果您发现这是您需要的东西,请file an enhancement request


关于您的实际代码,它本质上存在缺陷。做不是试图通过这种方式添加一个“rootViewController”setter到UIWindow。当你在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 
    } 
} 

买者实施者:我在浏览器窗口

+0

很酷,谢谢!那么,作为后续问题,代码看起来是否正确?这是我第一次通过类别添加伊娃。 – ma11hew28

+0

@MattDiPasquale编辑答案:) –

+0

嗯......是的,我无法编译它的iPhone 4.3.1设备。我得到了'为架构的ARMv6未定义的符号: “_OBJC_IVAR _ $ _ UIWindow._rootViewController”,从引用: - [一个UIWindow(添加)setRootViewController:在一个UIWindow + Additions.o LD:符号(S)没有发现架构的ARMv6 collect2:ld返回1退出状态。所以,我想为iOS 3.2创建子类。多一点代码,但是应该这样做!谢谢! – ma11hew28

-2

基于@大卫德隆的反馈输入这个,我去一个简单的小类如下:

// 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),因为我认为这看起来会是最有说服力的解决方案,并且可能是在工具箱中学习和使用的好技术。

+1

你还有我前面提到的iOS 5问题。编译时检查不能解决这个问题;它必须是一个运行时检查。 –

-1

下面是使用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 
0

根据@David DeLong's solution,这就是我想到的,它的工作原理非常漂亮。

基本上,我在UIWindow上做了一个类别。并在+load,我(运行时)检查是否为[UIWindow instancesRespondToSelector:@selector(rootViewController)]。如果不是,我使用class_addMethod()动态添加rootViewController的getter & setter方法。此外,我使用objc_getAssociatedObjectobjc_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, "[email protected]:@"); 
    } 
} 
#endif 

@end 
+0

这仍然是一个坏主意。 Apple Framework对象上的类别应始终在其方法名称的前面,以避免与将来或私有方法发生冲突。 – uchuugaka

相关问题