使用带有运行时反射的 NSCoder 和 NSKeyedArchiver 深度复制自定义 class

Using NSCoder and NSKeyedArchiver with runtime reflection to deep copy a custom class

我想使用

- (id)initWithCoder:(NSCoder *)coder

- (void)encodeWithCoder:(NSCoder *)coder

编码自定义 class 用于复制(使用 NSKeyedArchiver 等)

我的自定义 class 包含大量 iVar。我不想在编码代码中列出所有这些,而是​​想使用反射来简单地枚举循环中自定义 class 和 encode/decode 中每个 属性 中的所有属性。

我最终得到的代码看起来有点像这样:

- (id)initWithCoder:(NSCoder *)coder
{
    if ((self = [super init]))
    {
        [self codeWithCoder:coder andDirection:0];
    }
}
- (void)encodeWithCoder:(NSCoder *)coder
{
    [self codeWithCoder:coder andDirection:1];
}

-(void)codeWithCoder:(NSCoder *)coder andDirection:(NSInteger)direction
{
    unsigned count;
    objc_property_t *properties = class_copyPropertyList([self class], &count);



    unsigned i;
    for (i = 0; i < count; i++)
    {
        objc_property_t property = properties[i];
        NSString *name = [NSString stringWithUTF8String:property_getName(property)];
        const char * type = property_getAttributes(property);
        NSString * typeString = [NSString stringWithUTF8String:type];
        NSArray * attributes = [typeString componentsSeparatedByString:@","];
        NSString * typeAttribute = [attributes objectAtIndex:0];
        NSString * propertyType = [typeAttribute substringFromIndex:1];
        const char * rawPropertyType = [propertyType UTF8String];

        if (strcmp(rawPropertyType, @encode(float)) == 0) {
            //it's a float
            if (direction==0) ??? = [coder decodeFloatForKey:name];
            else [coder encodeFloat:???? forKey:name];

        } else if (strcmp(rawPropertyType, @encode(bool)) == 0) {
            //it's a bool
            if (direction==0) ??? = [coder decodeBoolForKey:name];
            else [coder encodeBool:???? forKey:name];

        } else if (strcmp(rawPropertyType, @encode(int)) == 0) {
            //it's an int
            if (direction==0) ??? = [coder decodeIntegerForKey:name];
            else [coder encodeInteger:???? forKey:name];

        } else if (strcmp(rawPropertyType, @encode(id)) == 0) {
            //it's some sort of object
            if (direction==0) ??? = [coder decodeObjectForKey:name];
            else [coder encodeObject:???? forKey:name];

        }
    }

    free(properties);





}

我的问题是:我应该用什么替换 ???和 ????用于获取和设置实际 ivar 值的片段?

你试过了吗:

object_setInstanceVariable
object_setIvar

https://developer.apple.com/library/ios/documentation/Cocoa/Reference/ObjCRuntimeRef/

您可能希望调用 class_copyIvarList 而不是 属性 版本 - 根据您的用例,仅获取属性可能会错过 IVars。你的 super class 也采用 NSCoding 吗?您必须调用 initWithCoder 和 encodeWithCoder 的超级实现。还要注意 class_copyIvarList 和 class_copyPropertyList 只是 return 当前定义的东西 class,而不是超 class.

基本上你使用objective-C的运行时API(class_copyIvarList)来获取对象的实例变量,然后检查每个实例变量的类型(ivar_getTypeEncoding), 并且 encode/decode 它根据类型动态变化。

性能 应该是一个问题,因为每次调用 class_copyIvarList 都非常耗时。库 DYCoding 很好地解决了这个问题,因为它使用 imp_implementationWithBlockclass_addMethod 将实现添加到 class,所以 class_copyIvarList 每个 [=30] 只调用一次=].

参考:可以查看https://github.com/flexme/DYCoding,它提供了动态encoding/decoding,而且和预编译代码一样快。

我们可以使用KVO来解决这个问题。

// decode
id value = [decoder decodeObjectForKey:name];
if (value != nil) {
    [self setValue:value forKey:name];
}

// encode
id value = [self valueForKey:name];
if ([value respondsToSelector:@selector(encodeWithCoder:)]) {
    [encoder encodeObject:value forKey:name];
}

参考:RuntimeNSCoding,给出了完整的解决方案。