2012-01-20 14 views
6

Tengo toneladas de objetos que quiero guardar para uso fuera de línea. Actualmente uso las clases que crean NSCoder para los objetos y los datos codificados para que el archivo esté disponible sin conexión.Forma fácil de hacer clase habilitada NSCoder

Así que en el .h que introducen los objetos:

@interface MyClass : NSObject<NSCoding>{ 
NSNumber* myObject;} 
@property(nonatomic,retain) NSNumber* myObject; 

Y en .m que hacen que el INIT:

- (id) initWithCoder: (NSCoder *)coder { 
    if (self = [super init]) { 
     [self setMyObject: [coder decodeObjectForKey:@"myObject"]]; 
    } 
} 

- (void) encodeWithCoder: (NSCoder *)coder { 
    [coder encodeObject: myObject forKey:@"myObject"]; 
} 

Así que la clase es sólo el almacenamiento maniquí con get y set. ¿Hay alguna forma mejor de hacer la decodificación/codificación? ¿Puedo utilizar de algún modo la codificación @dynamic o Key-value para codificar y decodificar? Básicamente quiero que todas las variables de la clase se guarden en el archivo y vuelvan a ser objeto cuando se inicia el programa. Este enfoque funciona, pero crear todas las clases requiere tiempo y esfuerzo.

Respuesta

43

Sí, puede hacer esto automáticamente. En primer lugar importar estos en su clase:

#import <objc/runtime.h> 
#import <objc/message.h> 

Ahora agregue este método, que utilizará métodos de bajo nivel para obtener los nombres de propiedades:

- (NSArray *)propertyKeys 
{ 
    NSMutableArray *array = [NSMutableArray array]; 
    Class class = [self class]; 
    while (class != [NSObject class]) 
    { 
     unsigned int propertyCount; 
     objc_property_t *properties = class_copyPropertyList(class, &propertyCount); 
     for (int i = 0; i < propertyCount; i++) 
     { 
      //get property 
      objc_property_t property = properties[i]; 
      const char *propertyName = property_getName(property); 
      NSString *key = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]; 

      //check if read-only 
      BOOL readonly = NO; 
      const char *attributes = property_getAttributes(property); 
      NSString *encoding = [NSString stringWithCString:attributes encoding:NSUTF8StringEncoding]; 
      if ([[encoding componentsSeparatedByString:@","] containsObject:@"R"]) 
      { 
       readonly = YES; 

       //see if there is a backing ivar with a KVC-compliant name 
       NSRange iVarRange = [encoding rangeOfString:@",V"]; 
       if (iVarRange.location != NSNotFound) 
       { 
        NSString *iVarName = [encoding substringFromIndex:iVarRange.location + 2]; 
        if ([iVarName isEqualToString:key] || 
         [iVarName isEqualToString:[@"_" stringByAppendingString:key]]) 
        { 
         //setValue:forKey: will still work 
         readonly = NO; 
        } 
       } 
      } 

      if (!readonly) 
      { 
       //exclude read-only properties 
       [array addObject:key]; 
      } 
     } 
     free(properties); 
     class = [class superclass]; 
    } 
    return array; 
} 

entonces aquí están sus métodos NSCoder:

- (id)initWithCoder:(NSCoder *)aDecoder 
{ 
    if ((self = [self init])) 
    { 
     for (NSString *key in [self propertyKeys]) 
     { 
      id value = [aDecoder decodeObjectForKey:key]; 
      [self setValue:value forKey:key]; 
     } 
    } 
    return self; 
} 

- (void)encodeWithCoder:(NSCoder *)aCoder 
{ 
    for (NSString *key in [self propertyKeys]) 
    { 
     id value = [self valueForKey:key]; 
     [aCoder encodeObject:value forKey:key]; 
    } 
} 

Tienes que ser un poco cuidadoso con esto. Existen las siguientes salvedades:

  1. Esto funcionará para propiedades que son números, Bools, objetos, etc, pero estructuras personalizados no funcionarán. Además, si alguna de las propiedades en su clase son objetos que no admiten temas de NSCoding, esto no funcionará.

  2. Esto solo funcionará con propiedades sintetizadas, no con ivars.

Se podría añadir gestión de errores mediante la comprobación del tipo de un valor en encodeWithCoder antes de codificarlo, o reemplazando el método setValueForUndefinedKey para manejar un problema con más gracia.

ACTUALIZACIÓN:

he envuelto estos métodos en una biblioteca: https://github.com/nicklockwood/AutoCoding - la biblioteca implementa estos métodos como una categoría en NSObject por lo que cualquier clase puede ser guardadas o cargadas, y también añade soporte para la codificación hereditaria propiedades, que mi respuesta original no maneja.

ACTUALIZACIÓN 2:

He actualizado la respuesta a tratar correctamente heredada y de sólo lectura propiedades.

+0

Gracias por una gran respuesta, esto es perfecto para mi propósito. Te daría un voto, pero no tengo suficiente reputación ... – MapaX

+0

El código tiene una pequeña pérdida de memoria. Debe agregar: gratis (propiedades); para método propertyKeys. – MapaX

+0

Buena llamada - Lo he arreglado. –

Cuestiones relacionadas