An easy way to make an NSCoder class

I have tons of objects that I want to save for offline use. I am currently using NSCoder compatible classes for objects and encoded data so that the file is available offline.

So, objects are introduced into .h:

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

And in .m I do inits:

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

So the class is just a dummy repository with a getter and setter. This is the best way to do decoding / encoding. Can I somehow use @dynamic or Key-value for encoding and decoding? Basically, I want all the variables in the class to be saved to a file and returned to the object when the program starts. This approach works, but creating all classes takes time and effort.

+6
source share
2 answers

Yes, you can do it automatically. First import them into your class:

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

Now add this method, which will use low-level methods to get property names:

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

The following are your NSCoder methods:

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

You have to be a little careful with this. The following reservations exist:

  • This will work for properties that are numbers, bools, objects, etc., but custom structures will not work. Also, if any properties of your class are objects that do not support NSCoding themes, this will not work.

  • This will only work with synthesized properties, not with ivars.

You can add error handling by checking the type of the value in encodeWithCoder before encoding it or by overriding the setValueForUndefinedKey method to handle the problem more elegantly.

UPDATE:

I combined these methods into a library: https://github.com/nicklockwood/AutoCoding - the library implements these methods as a category in NSObject, so any class can be saved or loaded, and also adds support for encoding inherited properties that my original answer did not handles.

UPDATE 2:

I updated the answer to properly deal with inherited and read-only properties.

+43
source

The https://github.com/nicklockwood/AutoCoding library is not very good, as it is reflected with the class_copyPropertyList every time, and it seems that it does not support some structures.

Check https://github.com/flexme/DYCoding , it should be as fast as the pre-compiled code, and supports various types of properties.

0
source

Source: https://habr.com/ru/post/906459/


All Articles