2012-01-20 11 views
6

Mam mnóstwo obiektów, które chcę zapisać do użytku w trybie offline. Obecnie używam klas zgodnych z NSCoder dla obiektów i zakodowanych danych do pliku, aby były dostępne w trybie offline.Łatwy sposób na NSCoder włączone klasy

Więc w .h mogę przedstawić obiekty:

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

A w .m robię inits:

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

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

Więc klasa jest tylko atrapa przechowywanie z getter i setter. Jest tutaj lepszy sposób na dekodowanie/kodowanie. Czy mogę użyć kodowania i dekodowania w jakiś sposób @dynamic lub Key-value? Zasadniczo chcę, aby wszystkie zmienne w klasie zapisane do pliku i powrócić do obiektu podczas uruchamiania programu. To podejście działa, ale tworzenie wszystkich klas wymaga czasu i wysiłku.

Odpowiedz

43

Tak, możesz to zrobić automatycznie. Najpierw zaimportować je w swojej klasie:

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

Teraz dodaj tę metodę, która będzie używać metod niskiego poziomu, aby uzyskać nazwy własności:

- (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; 
} 

to oto wasze metody 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]; 
    } 
} 

Musisz być trochę ostrożny z tym. Istnieją następujące zastrzeżenia:

  1. Będzie to działać dla właściwości liczb, boolów, obiektów itp., Ale niestandardowe elementy nie będą działać. Ponadto, jeśli któraś z właściwości twojej klasy to obiekty, które nie wspierają NSCoding, to nie zadziała.

  2. Działa to tylko z właściwościami syntetyzowanymi, a nie ivars.

Można dodać obsługę błędów sprawdzając typ wartości w encodeWithCoder przed kodowaniem go lub nadpisanie metody setValueForUndefinedKey obsłużyć problem bardziej wdzięcznie.

UPDATE:

mam owinięty tych metod góry do biblioteki: https://github.com/nicklockwood/AutoCoding - biblioteka implementuje te metody jako kategoria na NSObject więc każda klasa może być zapisane lub załadowane, a także dodaje wsparcie dla kodowania dziedziczone właściwości, których moja oryginalna odpowiedź nie obsługuje.

UPDATE 2:

zaktualizowałem odpowiedź do czynienia z prawidłowo dziedziczone i tylko do odczytu właściwości.

+0

Dzięki za wspaniałą odpowiedź, jest to idealne dla mojego celu. Chciałbym dać ci głos, ale nie mam wystarczającej reputacji .. – MapaX

+0

Kod ma mały wyciek pamięci. Musisz dodać: za darmo (właściwości); do metody propertyKeys. – MapaX

+0

Dobra rozmowa - naprawiłem to. –

0

Wydajność biblioteki nie jest dobra, ponieważ wykonuje odbicie za pomocą klasy class_copyPropertyList za każdym razem i wygląda na to, że nie obsługuje niektórych struktur.

Sprawdź kod https://github.com/flexme/DYCoding, powinien być tak szybki, jak kod skompilowany i obsługuje różne typy właściwości.