使用带有运行时反射的 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_implementationWithBlock
和 class_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,给出了完整的解决方案。
我想使用
- (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_implementationWithBlock
和 class_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,给出了完整的解决方案。