I have tons of objects which I want to save for offline use. Currently I use create NSCoder compliant classes for the objects and coded data to file to be available offline.
So in the .h I introduce the objects:
@interface MyClass : NSObject<NSCoding>{
NSNumber* myObject;}
@property(nonatomic,retain) NSNumber* myObject;
And in .m I make the 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 dummy storage with getter and setter. Is here any better way to do the decode / encode. Can I use somehow @dynamic or Key-value coding for encode and decode? Basically I want all the variables in class saved to file and back to object when program starts up. This approach work, but creating all classes takes time and effort.
NSCoder declares the interface used by concrete subclasses to transfer objects and other values between memory and some other format. This capability provides the basis for archiving (storing objects and data on disk) and distribution (copying objects and data items between different processes or threads).
The NSCoding protocol declares the two methods that a class must implement so that instances of that class can be encoded and decoded. This capability provides the basis for archiving (where objects and other structures are stored on disk) and distribution (where objects are copied to different address spaces).
Yes, you can do this automatically. First import these into your class:
#import <objc/runtime.h>
#import <objc/message.h>
Now add this method, which will use low-level methods to get the 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;
}
Then here 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 bit careful with this. There are the following caveats:
This will work for properties that are numbers, bools, objects, etc, but custom structs won't work. Also, if any of the properties in your class are objects that don't themeselves support NSCoding, this won't work.
This will only work with synthesized properties, not ivars.
You could add error handling by checking the type of a value in encodeWithCoder before encoding it, or overriding the setValueForUndefinedKey method to handle a problem more gracefully.
UPDATE:
I've wrapped these methods up into a library: https://github.com/nicklockwood/AutoCoding - the library implements these methods as a category on NSObject so any class can be saved or loaded, and it also adds support for coding inherited properties, which my original answer doesn't handle.
UPDATE 2:
I've updated the answer to correctly deal with inherited and read-only properties.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With