【问题标题】:Objective-C Reflection for generic NSCoding implementation用于通用 NSCoding 实现的 Objective-C 反射
【发布时间】:2009-05-28 05:11:46
【问题描述】:
Objective-C 中是否有任何反射方法允许您通过检查对象的公共属性并生成 encodeWithCoder: 和 initWithCoder: 的通用实现来编写通用 NSCoding 实现。
我正在考虑类似XStream for Java 的东西,它允许使用反射来序列化和反序列化 Java 对象的通用方法。更好的方法可能是将属性标记为您想要序列化或瞬态的东西(如 Java 中的瞬态关键字)。
我一直在阅读 Archives and Serializations Programming Guide 上有关 Cocoa 的文档。我知道您希望对对象的序列化进行一些控制,但这通常是一个对称的过程,必须反转为序列化编码的内容以反序列化它似乎很奇怪。我是 DRY 的信徒(不要重复自己)。
【问题讨论】:
标签:
iphone
objective-c
reflection
serialization
objective-c-runtime
【解决方案1】:
这不仅是可能的,而且我有一个朋友正在努力做到这一点。 (您可以查看他的blog about it here。)反射是使用Objective-C 2.0 Runtime Reference 中记录的Objective-C 运行时函数完成的。看看吧。
但是请注意,这只有在您想要保存所有实例变量的通用行为时才有效。不过,您可能不希望 NSView 保存它的超级视图;在这种情况下,通用情况将不起作用。
您可以通过为要保存的任何实例变量声明属性并将任何其他变量“隐藏”来区分要序列化的事物和不可序列化的事物,但这将属性的全部目的扭曲为一个小好处。我不会推荐它。
【解决方案2】:
(我是 BJ Homer 评论中链接的博客文章的作者)。使用代码有几个注意事项:
- 目标类必须符合 KVC。如果您为您的 ivars 使用 Objective-C 2.0 属性,那么您已经准备就绪。否则,您需要确保您的类设置为正确响应 valueForKey: 和 setValue:forKey: 方法。
- 代码使用运行时中未记录的“功能”以递归方式使 ivar 类符合 NSCoding。编译时,几乎所有的输入信息 (AFAIK) 都会从代码中删除,但为了保存 ivars,它们 也必须符合 NSCoding。我发现如果 ivar 是一个对象并且我在其上调用 ivar_getTypeEncoding(),我会得到一个 c 字符串,它看起来像:{#IVAR_CLASS}(示例:{#NSString})。我所做的是获取 # 和 } 之间的字符串,使用 NSClassFromString 创建一个 Class 对象,然后对其进行递归。
- 此代码将保存所有 ivars。
- 使用风险自负(当然)
我写这个主要是为了证明概念,在很多情况下这段代码会严重失败。例如,在对象 A 中有一个用于 B 的 ivar,而 B 有一个用于 A 的 ivar,然后使用代码序列化其中一个或另一个会(我相信)导致无限循环。
尽管如此,我认为 Objective-C 甚至允许你做类似的事情,这真是太棒了。
【解决方案4】:
今晚写这篇文章就是为了这个目的;不要忘记#import!这假设你的类中的所有@properties 都是 NSObject 形式,所以 NSNumber 而不是 float 等。
#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);
}