2011-07-07 10 views
15

En el Key-Value Observing Programming Guide, la sección Registering for Key-Value Observing dice "Normalmente, las propiedades en los marcos suministrados por Apple solo son compatibles con KVO si están documentadas como tales". Pero, no he encontrado ninguna propiedad en la documentación que esté documentada como compatible con KVO. ¿Podrías dirigirme a algunos?iOS: ¿Cómo puedo saber si una propiedad cumple con KVO?

Específicamente, me gustaría saber si el @property(nonatomic,retain) UIViewController *rootViewController de UIWindow es compatible con KVO. La razón es que estoy agregando la propiedad rootViewController al UIWindow para iOS < 4 y quiero saber si debo hacerlo compatible con 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 

Respuesta

16

Respuesta corta: No.

Respuesta larga: Nada en UIKit se garantiza que sea compatible con MVA. Si descubre que funciona una propiedad KVO-ing, sea agradecido, no es intencional. Además: ten cuidado. Podría muy bien romperse en el futuro.

Si encuentra que es algo que necesita, por favor file an enhancement request.


Acerca de su código actual, es inherentemente defectuoso. Haga NO intente agregar un setter "rootViewController" al UIWindow de esta manera. Se se se rompe cuando compila su código en iOS 4, pero alguien lo ejecuta en un dispositivo iOS 5. Debido a que compiló utilizando el SDK 4.x, las declaraciones #if se evaluarán como verdaderas, lo que significa que su método smasher de categoría se incluirá en el binario. Sin embargo, cuando lo ejecuta en un dispositivo iOS 5, ahora obtendrá un conflicto de método porque dos métodos en UIWindow tendrán la misma firma de método, y no hay garantía de cuál se usará.

No atornille con los marcos como este. Si tiene que tener esto, use una subclase. ESTO ES POR QUÉ EXISTE EL SUBCLASEO.


Su subclase sería algo como esto:

@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 
    } 
} 

Advertencia Implementor: Me escribió esto en una ventana del navegador

+0

Genial, gracias! Entonces, como una pregunta de seguimiento, ¿el código se ve correcto y bueno? Esta es la primera vez que agrego un ivar a través de una categoría. – ma11hew28

+0

@MattDiPasquale edited answer :) –

+0

Hmm ... Sí, no pude compilarlo para el dispositivo iPhone 4.3.1. Tengo 'símbolos no definidos para la arquitectura ARMv6: "_OBJC_IVAR _ $ _ UIWindow._rootViewController", se hace referencia a partir de: - [UIWindow (adiciones) setRootViewController:] en UIWindow + Additions.o ld: Símbolo (s) no encontrado para la arquitectura ARMv6 collect2: ld devolvió 1 estado de salida'. Por lo tanto, voy a subclasificar para iOS 3.2, supongo. Un poco más de código, ¡pero debería hacerlo! ¡Gracias! – ma11hew28

-2

base en la retroalimentación de @ David DeLong, fui con una subclase simple como así:

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

Sin embargo, esto también requiere pasar por el código existente y usar la compilación condicional para convertir UIWindow en UIWindow3 donde se haya accedido a rootViewController. (Nota: creo que la solución de @David DeLong puede no requerir la realización de estos cambios adicionales, sino simplemente usar siempre CustomWindow en lugar de UIWindow). Por lo tanto, esto es más molesto que si pudiera (solo para iOS < 4) simplemente agregue el rootViewController al UIWindow a través de una categoría. Puedo buscar hacer esto con un category using Associative References (solo para iOS < 4) porque creo que parece ser la solución más elocuente y podría ser una buena técnica para aprender y tener en la caja de herramientas.

+1

Todavía tiene los problemas de iOS 5 que mencioné anteriormente. No puede resolver ese problema con una verificación en tiempo de compilación; tiene que ser un control en tiempo de ejecución. –

-1

Aquí hay una solución usando Associative References to define an instance variable with a category.Pero, no funciona porque, de acuerdo con @Dave DeLong, debo usar un run-time (not compile-time) check para esto.

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

Basado en @David DeLong's solution, esto es lo que me ocurrió, y funciona muy bien.

Básicamente, hice una categoría en UIWindow. Y en +load, I (tiempo de ejecución) compruebe si [UIWindow instancesRespondToSelector:@selector(rootViewController)]. Si no, utilizo class_addMethod() para agregar dinámicamente los métodos getter & setter para rootViewController. Además, uso objc_getAssociatedObject y objc_setAssociatedObject para obtener & establezca el rootViewController como una variable de instancia 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, "[email protected]:@"); 
    } 
} 
#endif 

@end 
+0

Esto sigue siendo una mala idea. Las categorías de los objetos de Apple Framework siempre deben incluir el prefijo de los nombres de sus métodos para evitar conflictos con métodos futuros o privados. – uchuugaka

Cuestiones relacionadas