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

僤鯓⒐⒋嵵緔 提交于 2019-12-25 04:16:05

问题


I'd like to use

- (id)initWithCoder:(NSCoder *)coder

and

- (void)encodeWithCoder:(NSCoder *)coder

to encode a custom class for copying (using NSKeyedArchiver etc)

My custom class contains a large number of iVars. Instead of listing all of these in the encoding code I'd like to use reflection to simply enumerate over all the properties in the custom class and encode/decode each property from within a loop.

I'll end up with code that looks a bit like this:

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





}

My question is this: what do I replace the ??? and ???? fragments with to get and set the actual ivar values?


回答1:


Did you try:

object_setInstanceVariable
object_setIvar

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

You might want to be calling class_copyIvarList instead of the property version - just getting properties might miss IVars depending on your use case. Also does your super class adopt NSCoding? You'll have to call the super implementations of initWithCoder and encodeWithCoder. Note also that class_copyIvarList and class_copyPropertyList only return things defined in the current class, not the super class.




回答2:


Basically you use the objective-C's runtime API (class_copyIvarList) to get the instance variables for the object, then check the types of each instance variable (ivar_getTypeEncoding), and encode/decode it dynamically based on the types.

Performance should be a concern since invoking class_copyIvarList every time is time consuming. The library DYCoding addresses it well since it uses imp_implementationWithBlock and class_addMethod to add the implementation to the class, so class_copyIvarList is only invoked once per class.

Reference: You can check https://github.com/flexme/DYCoding , it provides the dynamic encoding/decoding, and it is as fast as the precompiled code.




回答3:


We can use KVO to solve this.

// 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];
}

Reference: RuntimeNSCoding, it shows a complete solution.



来源:https://stackoverflow.com/questions/28054122/using-nscoder-and-nskeyedarchiver-with-runtime-reflection-to-deep-copy-a-custom

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!