轻松实现NSCoder启用课程

时间:2012-01-20 07:21:09

标签: objective-c ios key-value nscoder

我有大量的对象需要保存以供离线使用。 目前,我使用创建NSCoder兼容类的对象和编码数据文件,以便脱机使用。

所以在.h中我介绍了这些对象:

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

在.m中我做了一些内容:

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

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

所以这个类只是带有getter和setter的虚拟存储。 这里有更好的方法来进行解码/编码。 我能以某种方式使用@dynamic或键值编码进行编码和解码吗? 基本上我希望类中的所有变量都保存到文件中,并在程序启动时返回到对象。 这种方法有效,但创建所有类需要花费时间和精力。

2 个答案:

答案 0 :(得分:43)

是的,您可以自动执行此操作。首先将这些导入您的班级:

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

现在添加此方法,它将使用低级方法获取属性名称:

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

然后是你的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];
    }
}

你必须要小心一点。有以下警告:

  1. 这适用于数字,bool,对象等属性,但自定义结构不起作用。此外,如果您班级中的任何属性是不支持NSCoding的对象,则不起作用。

  2. 这只适用于合成属性,而不适用于ivars。

  3. 您可以通过在编码之前检查encodeWithCoder中的值的类型来添加错误处理,或者覆盖setValueForUndefinedKey方法以更优雅地处理问题。

    更新:

    我已将这些方法包装到库中:https://github.com/nicklockwood/AutoCoding - 库将这些方法实现为NSObject上的类别,因此可以保存或加载任何类,并且还增加了对编译继承属性的支持,我的原始答案没有处理。

    更新2:

    我已经更新了正确处理继承和只读属性的答案。

答案 1 :(得分:0)

https://github.com/nicklockwood/AutoCoding的性能不好,因为它每次都使用class_copyPropertyList进行反射,而且它似乎不支持某些结构。

检查https://github.com/flexme/DYCoding,它应该与预编译的代码一样快,并且它支持各种属性类型。