6

En pocas palabras, ¿hay alguna manera de recibir una notificación general cuando se cambia una propiedad en una clase Objective-C? Sé que puedo usar KVO para monitorear cambios de propiedad particulares, pero tengo la necesidad de llamar a un método en particular cada vez que se envía un mensaje setProperty: a mi clase. Deseo poder recibir una notificación genérica sin ninguna preocupación sobre qué propiedad en particular se modificó.Observación de un cambio en CUALQUIER propiedad de clase en Objective-C

Si ayuda a aclarar por qué quiero hacer esto, estoy haciendo uso de un código de desplazamiento de tablas rápidas encontrado aquí: http://blog.atebits.com/2008/12/fast-scrolling-in-tweetie-with-uitableview/

Parte del proceso de lograr esto es que cada vez que una propiedad en una vista de tabla la celda se modifica, se debe llamar al [ self setNeedsDisplay ]. Prefiero no tener que anular los métodos setter para cada propiedad de mi clase solo para hacer esta llamada.

Respuesta

5

Como señala Chuck, puede crear una clave dependiente o, por supuesto, puede observar directamente todas las propiedades (lo cual es menos trabajo que sobrecargar a los incubadores).

Usando el tiempo de ejecución de Objective-C, si usa propiedades exclusivamente, puede automatizar este proceso usando class_copyPropertyList(). Pero probablemente solo haga esto si este problema surge un poco para ti. Si solo tiene una instancia de este problema, probablemente sea más fácil y más segura y más fácil de mantener solo para observar directamente la lista de propiedades a menos que tenga ganas de trabajar en el tiempo de ejecución de ObjC.

2

No exactamente. Puede crear un dependent key que depende de cada propiedad que desee exponer y luego observarlo. Creo que es lo más cercano que obtendrás.

+0

Hm. Parece que abusaría del concepto de claves dependientes en este caso. Supongo que solo observaré cada propiedad individualmente. Podría haber jurado que había una manera genérica de hacer esto. ¿Qué tal una forma de captar el nombre del mensaje que se pasa a una clase a través del tiempo de ejecución? – LucasTizma

+0

No realmente.Simplemente llame a la clave dependiente 'propertiesChanged' o algo así y será bastante preciso. mmalc en Apple realmente sugiere esta técnica en su Cocoa Bindings Example and Hints: http://homepage.mac.com/mmalc/CocoaExamples/controllers.html ... En cuanto a "captar el nombre del mensaje que se pasa a una clase", supongamos que se refiere a capturar * todos * mensajes enviados a una clase. Esto requeriría un objeto proxy o un enlace a la función de tiempo de ejecución 'objc_msgSend()', la última de las cuales sería masivamente lenta y hackosa y realmente no vale la pena. – Chuck

4

He aquí un ejemplo construido fuera de las sugerencias de la tirada y de Rob:

DrakeObject.h

@interface DrakeObject : NSObject 

@property (nonatomic, strong) NSNumber *age; 
@property (nonatomic, strong) NSNumber *money; 
@property (nonatomic, strong) NSString *startPosition; 
@property (nonatomic, strong) NSString *currentPosition; 
@property (nonatomic, strong, readonly) id propertiesChanged; 

@end 

DrakeObject.m

@implementation DrakeObject 

- (instancetype)init { 
    self = [super init]; 
    if (self) { 
     self.age = @25; 
     self.money = @25000000; 
     self.startPosition = @"bottom"; 
     self.currentPosition = @"here"; 
    } 
    return self; 
} 

- (id)propertiesChanged { 
    return nil; 
} 

+(NSSet *)keyPathsForValuesAffectingPropertiesChanged { 
    return [NSSet setWithObjects:@"age", @"money", @"startPosition", @"currentPosition", nil]; 
} 

observar propertiesChanged nos permitirá saber en cualquier momento una propiedad ha cambiado .

[self.drakeObject addObserver:self 
        forKeyPath:@"propertiesChanged" 
         options:NSKeyValueObservingOptionNew 
         context:nil]; 
0

Aquí un ejemplo de código. Tengo un objeto general y otro objeto. El objeto Dother debe guardar su estado al cambiar cada propiedad.

#import <Foundation/Foundation.h> 

@interface GeneralObject : NSObject 

+ (instancetype)instanceWithDictionary:(NSDictionary *)aDictionary; 
- (instancetype)initWithDictionary:(NSDictionary *)aDictionary; 
- (NSDictionary *)dictionaryValue; 
- (NSArray *)allPropertyNames; 

@end 

aplicación

#import "GeneralObject.h" 
#import <objc/runtime.h> 

@implementation GeneralObject 

#pragma mark - Public 

+ (instancetype)instanceWithDictionary:(NSDictionary *)aDictionary { 
    return [[self alloc] initWithDictionary:aDictionary]; 
} 

- (instancetype)initWithDictionary:(NSDictionary *)aDictionary { 
    aDictionary = [aDictionary clean]; 

    for (NSString* propName in [self allPropertyNames]) { 
     [self setValue:aDictionary[propName] forKey:propName]; 
    } 

    return self; 
} 

- (NSDictionary *)dictionaryValue { 
    NSMutableDictionary *result = [NSMutableDictionary dictionary]; 
    NSArray *propertyNames = [self allPropertyNames]; 

    id object; 
    for (NSString *key in propertyNames) { 
     object = [self valueForKey:key]; 
     if (object) { 
      [result setObject:object forKey:key]; 
     } 
    } 

    return result; 
} 

- (NSArray *)allPropertyNames { 
    unsigned count; 
    objc_property_t *properties = class_copyPropertyList([self class], &count); 

    NSMutableArray *array = [NSMutableArray array]; 

    unsigned i; 
    for (i = 0; i < count; i++) { 
     objc_property_t property = properties[i]; 
     NSString *name = [NSString stringWithUTF8String:property_getName(property)]; 
     [array addObject:name]; 
    } 

    free(properties); 

    return array; 
} 

@end 

y después de todo lo que tenemos dother clase, que debe salvar a su estado de cada cambio de cualquier propiedad

#import "GeneralObject.h" 

extern NSString *const kUserDefaultsUserKey; 

@interface DotherObject : GeneralObject 

@property (strong, nonatomic) NSString *firstName; 
@property (strong, nonatomic) NSString *lastName; 
@property (strong, nonatomic) NSString *email; 

@end 

e implementación

#import "DotherObject.h" 

NSString *const kUserDefaultsUserKey = @"CurrentUserKey"; 

@implementation DotherObject 

- (instancetype)initWithDictionary:(NSDictionary *)dictionary { 
    if (self = [super initWithDictionary:dictionary]) { 
     for (NSString *key in [self allPropertyNames]) { 
      [self addObserver:self forKeyPath:key options:NSKeyValueObservingOptionNew context:nil]; 
     } 
    } 

    return self; 
} 

- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context { 
    NSDictionary *dict = [self dictionaryValue]; 
    [[NSUserDefaults standardUserDefaults] setObject:dict forKey:kUserDefaultsUserKey]; 
    [[NSUserDefaults standardUserDefaults] synchronize]; 
} 

- (NSString *)description { 
    return [NSString stringWithFormat:@"%@; dict:\n%@", [super  description], [self dictionaryValue]]; 
} 

@end 

Happy coding!

Cuestiones relacionadas