使用 NSCoder 和 NSKeyedArchiver 和运行时反射来深度复制自定义类



我想使用

- (id)initWithCoder:(NSCoder *)coder

- (void)encodeWithCoder:(NSCoder *)coder

对自定义类进行编码以进行复制(使用NSKeyedArchiver等)

我的自定义类包含大量 iVar。 与其在编码代码中列出所有这些,我想使用反射来简单地枚举自定义类中的所有属性,并从循环中对每个属性进行编码/解码。

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

- (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,具体取决于您的用例。 你的超级类也采用NSCoding吗? 你必须调用 initWithCoder 和 encodeWithCoder 的超级实现。 另请注意,class_copyIvarList 和 class_copyPropertyList 只返回当前类中定义的内容,而不返回超类中定义的内容。

基本上,您使用 objective-C 的运行时 API ( class_copyIvarList ) 获取对象的实例变量,然后检查每个实例变量的类型 ( ivar_getTypeEncoding ),并根据类型动态编码/解码。

性能应该是一个问题,因为每次调用class_copyIvarList都很耗时。库DYCoding很好地解决了这个问题,因为它使用 imp_implementationWithBlockclass_addMethod 将实现添加到类中,因此每个类只调用一次class_copyIvarList

参考:可以查 https://github.com/flexme/DYCoding,它提供动态编码/解码,速度和预编译代码一样快。

我们可以使用 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];
}

参考:运行时NSCoding,它显示了一个完整的解决方案。

最新更新