IOS: Как узнать, соответствует ли свойство KVO?
В Руководстве по наблюдению за ключевыми значениями раздел Регистрация для наблюдения за ключевыми значениями говорит: Обычно свойства в поставляемых Apple фреймворках являются только KVO-совместимыми, если они документируются как таковые ". Но я не нашел никаких свойств в документации, которые задокументированы как KVO-совместимые. Не могли бы вы указать мне некоторые?
В частности, я хотел бы знать, соответствует ли @property(nonatomic,retain) UIViewController *rootViewController
of UIWindow
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
Ответы
Ответ 1
Короткий ответ: No.
Длинный ответ: ничто в UIKit не гарантирует соответствия KVO. Если вы обнаружите, что KVO-ing собственности работает, будьте благодарны, это непреднамеренно. Также: будьте осторожны. В будущем это может сильно разорваться.
Если вы обнаружите, что это то, что вам нужно, напишите запрос на улучшение.
О вашем реальном коде, он по своей сути ошибочен. НЕ пытайтесь добавить установщик "rootViewController" в UIWindow
таким образом. Он будет разбиваться, когда вы компилируете свой код на iOS 4, но кто-то запускает его на устройстве iOS 5. Поскольку вы скомпилировали с использованием SDK 4.x, операторы #if
будут оценивать значение true, что означает, что ваш smashher метода категории будет включен в двоичный файл. Однако, когда вы запускаете его на устройстве 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
}
}
Caveat Implementor: я набрал это в окне браузера
Ответ 2
Основываясь на @David DeLong решении, это то, что я придумал, и он прекрасно работает.
В принципе, я сделал категорию на UIWindow
. И в +load
, я (время выполнения) проверяет, есть ли [UIWindow instancesRespondToSelector:@selector(rootViewController)]
. Если нет, я использую class_addMethod()
для динамического добавления методов getter и setter для rootViewController
. Кроме того, я использую 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, "[email protected]:@");
}
}
#endif
@end
Ответ 3
Здесь используется решение с использованием Ассоциативные ссылки для определения переменной экземпляра с категорией. Но, это не работает, по словам @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
Ответ 4
Основываясь на обратной связи @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, используя Ассоциативные ссылки (только для iOS < 4), потому что я думаю, что похоже, что это будет наиболее красноречивое решение и может быть хорошей техникой для изучения и использования в панели инструментов.