iOS: Comment savoir si une propriété est conforme à la norme KVO?

Dans le Guide de programmation d'observation des valeurs-keys , la section Enregistrement pour l'observation des valeurs-keys indique que «les propriétés des infrastructures fournies par Apple sont généralement conforms à la norme KVO uniquement si elles sont documentées en tant que telles. Mais, je n'ai trouvé aucune propriété dans la documentation qui soit documentée comme conforme à la norme KVO. Pourriez-vous m'en indiquer quelques-unes?

Plus précisément, j'aimerais savoir si le @property(nonatomic,retain) UIViewController *rootViewController est UIWindow KVO. La raison en est que rootViewController propriété rootViewController à UIWindow pour iOS <4 et je veux savoir si je devrais la rendre compatible avec le 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 

Réponse courte: Non.

Réponse longue: Rien dans UIKit n'est garanti conforme à la norme KVO. Si vous trouvez que KVO-une propriété fonctionne, soyez reconnaissants, c'est involontaire. Aussi: méfiez-vous. Cela pourrait très bien se briser à l'avenir.

Si vous trouvez que c'est quelque chose dont vous avez besoin, veuillez déposer une request d'amélioration .


À propos de votre code actuel, il est insortingnsèquement erroné. N'essayez PAS d'append un setter "rootViewController" à UIWindow cette façon. Il se casse quand vous comstackz votre code sur iOS 4 mais quelqu'un l'exécute sur un appareil iOS 5. Comme vous avez compilé en utilisant le SDK 4.x, les instructions #if seront vraies, ce qui signifie que votre méthode de catégorie smasher sera incluse dans le binary. Cependant, lorsque vous l'exécutez sur un appareil iOS 5, vous allez maintenant get un conflit de méthode car deux methods sur UIWindow auront la même signature de méthode, et il n'y a aucune garantie quant à celle qui sera utilisée .

Ne pas visser avec les frameworks comme celui-ci. Si vous devez l'avoir, utilisez une sous-class. C'EST POURQUOI LE SOUS-CLASSEMENT EXISTE.


Votre sous-class ressemblerait à ceci:

 @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 comstack 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 comstack 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 : J'ai tapé ceci dans une window de browser

Basé sur la solution de @David DeLong , c'est ce que j'ai trouvé, et ça marche très bien.

Fondamentalement, j'ai fait une catégorie sur UIWindow . Et dans +load , je (run-time) vérifie si [UIWindow instancesRespondToSelector:@selector(rootViewController)] . Sinon, j'utilise class_addMethod() pour append dynamicment les methods getter et setter pour rootViewController . En outre, j'utilise objc_getAssociatedObject et objc_setAssociatedObject pour get & définir rootViewController tant que variable d'instance de 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 

Voici une solution utilisant les references associatives pour définir une variable d'instance avec une catégorie . Mais, cela ne marche pas, d'après @Dave DeLong, je dois utiliser un contrôle d' exécution (pas de compilation) pour cela.

 // 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 

Basé sur les commentaires de @David DeLong, je suis allé avec une sous-class simple comme ça:

 // 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 

Cependant, cela nécessitait également de passer par le code existant et d'utiliser une compilation conditionnelle pour lancer UIWindow vers UIWindow3 à UIWindow3 UIWindow à UIWindow3 . (Note: Je pense que la solution de @David DeLong ne nécessite pas de faire ces changements supplémentaires, mais utilise CustomWindow au lieu de UIWindow .) Ainsi, c'est plus ennuyeux que si je pouvais (seulement pour iOS <4) append rootViewController à UIWindow via une catégorie. Je pourrais envisager de le faire avec une catégorie utilisant des references associatives (uniquement pour iOS <4) car je pense que cela semble être la solution la plus eloquent et pourrait être une bonne technique à apprendre et avoir dans la boîte à outils.