iOS:我如何知道某个属性是否符合KVO标准?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS:我如何知道某个属性是否符合KVO标准?相关的知识,希望对你有一定的参考价值。

Key-Value Observing Programming Guide中,Registering for Key-Value Observing部分说“通常,Apple提供的框架中的属性只有符合KVO标准才会被记录。”但是,我没有在文档中找到任何记录为KVO兼容的属性。你能指点一下吗?

具体来说,我想知道@property(nonatomic,retain) UIViewController *rootViewControllerUIWindow是否符合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_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, "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),因为我觉得它看起来像是最有说服力的解决方案,可能是一个很好的技术,可以在工具箱中学习和使用。

以上是关于iOS:我如何知道某个属性是否符合KVO标准?的主要内容,如果未能解决你的问题,请参考以下文章

使用Block实现KVO

ios KVO的实现原理

iOS KVO详解

iOS KVO详解

iOSKVC 与 KVO

哪些类不符合 KVO?