Objective-C Reflection for generic NSCoding implementation
Asked Answered
A

4

12

Is there any means of reflection in Objective-C that would allow you to write generic NSCoding implementations by inspecting the public properties of an object and generating generic implementations of encodeWithCoder: and initWithCoder: .

I'm thinking of something like XStream for Java that allows a generic way to serialize and deserialize Java objects using reflection. Even better would probably be some means of marking properties as things you'd want to serialize or that are transient (like the transient keyword in Java).

I've been reading the documentation on Archives and Serializations Programming Guide for Cocoa. I understand that you want some control over the serialization of your objects, but it is generally a symmetrical process and it seems odd to have to reverse what is coded for serialization to deserialize it. I'm a believer of DRY (don't repeat yourself).

Attorn answered 28/5, 2009 at 5:11 Comment(1)
Selected answer links to a site that no longer exists as its answer. Please update the selected answer accordingly. If it helps, I've written methods that will allow for what you are asking here: https://mcmap.net/q/928388/-objective-c-reflection-for-generic-nscoding-implementationJelly
S
12

Not only is it possible, but I have a friend who's taken a stab at doing precisely that. (You can see his blog about it here.) The reflection is done using the Objective-C runtime functions documented in the Objective-C 2.0 Runtime Reference. Take a look.

Note, however, that this will only work if you want the generic behavior of saving all the instance variables. You might not want an NSView to save its superview, though; in such cases, the generic case wouldn't work.

You could conceivably distinguish between things-to-serialize and things-not-to-serialize by declaring properties for any instance variables you want to save and leaving any other variables "hidden", but that's twisting the whole purpose of properties to a small benefit. I wouldn't recommend it.

Scottscotti answered 28/5, 2009 at 5:20 Comment(0)
U
6

(I am the author of the blog post linked to in BJ Homer's comment). There are a couple caveats to using the code:

  1. The target class has to be KVC-compliant. If you're using Objective-C 2.0 properties for your ivars, then you're all set. Otherwise, you need to make sure that your class is set up to properly respond to valueForKey: and setValue:forKey: methods.
  2. The code uses an undocumented "feature" of the runtime to recursively conform ivar classes to NSCoding. When compiled, almost all typing information (AFAIK) is stripped out of the code, yet in order to save the ivars, they have to conform to NSCoding as well. I discovered that if the ivar is an object and I call ivar_getTypeEncoding() on it, I'll get a c string back that looks something like: {#IVAR_CLASS} (Example: {#NSString}). What I do is get the string between the # and }, create a Class object using NSClassFromString, and recurse on that.
  3. This code will save ALL the ivars.
  4. Use at your own risk (of course)

I wrote this mainly as a proof of concept, and there are many instances when this code would fail spectacularly. For example, in object A has an ivar for B, and B has an ivar for A, then using the code to serialize one or the other would (I believe) cause an infinite loop.

Nevertheless, I think it's pretty freaking awesome that Objective-C even allows you to do stuff like in the first place.

Uprush answered 2/6, 2009 at 2:11 Comment(0)
C
0

Check out RMModelObject:

http://www.realmacforge.com/svn/trunk/RMModelObject/

However, note that ObjC Runtime stuff works in 10.5, and in the Simulator, but not on an actual iPhone. Found that out the hard way.. Maybe OS 3.0 finally supports it, but I haven't checked.

Cavorelievo answered 2/7, 2009 at 23:18 Comment(0)
J
0

Wrote this tonight for this very purpose; don't forget the #import! This assumes all @properties in your class are NSObject-form, so NSNumber instead of float, etc.

#import <objc/runtime.h>

-(id)initWithCoder:(NSCoder *)decoder {
    if (self = [super init]) {
        uint count;
        objc_property_t *properties = class_copyPropertyList(self.class, &count);
        for (int i = 0; i < count ; i++) {
            const char* propertyName = property_getName(properties[i]);
            NSString *key = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding];
            NSValue *value = [decoder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
        free(properties);
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)encoder {
    uint count;
    objc_property_t *properties = class_copyPropertyList(self.class, &count);
    for (int i = 0; i < count ; i++) {
        const char* propertyName = property_getName(properties[i]);
        NSString *key = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding];
        NSValue *value = [self valueForKey:key];
        [encoder encodeObject:value forKey:key];
    }
    free(properties);
}
Jelly answered 10/10, 2018 at 7:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.