6

我有大量的对象要保存以供离线使用。目前,我使用创建符合 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 或键值编码进行编码和解码吗?基本上我希望类中的所有变量在程序启动时保存到文件并返回到对象。这种方法有效,但创建所有类需要时间和精力。

4

2 回答 2

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. 这适用于数字、布尔值、对象等属性,但自定义结构不起作用。此外,如果您的类中的任何属性是本身不支持 NSCoding 的对象,则这将不起作用。

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

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

更新:

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

更新 2:

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

于 2012-01-20T07:31:16.727 回答
0

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

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

于 2016-07-12T04:38:23.903 回答