如何使用 objective-c 运行时复制实例
How to make a copy of a instance with objective-c runtime
在我的项目中有一个 class 有很多变量,现在我希望它符合 NSCopying
协议,所以我必须 "copy" [=14] 中的每个变量=].如果变量是一个对象,发送副本给它,如果它是一个标量值,只需使用赋值符号。
class可能会经常变化,这意味着我可能会在之后添加一些变量,然后我或其他人可能会忘记修改- (id)copyWithZone:(NSZone *)zone
方法中的代码。为了避免这种情况,我正在考虑使用 objective-c 的运行时功能来完成这项工作。
下面是Class的一部分,class中的变量是一个对象或标量值。你会注意到一个unsigned int数组,它是一个例外,你可以省略它,因为当变量的类型编码为[10I]
.
时我可以手动复制它
@interface DataObject : NSObject <NSCopying>
{
@public
unsigned int arrPrice[10];
}
@property (nonatomic, copy) NSString *code ;
@property (nonatomic, assign) unsigned char type ;
@property (nonatomic, assign) int digit ;
@property (nonatomic, assign) unsigned int count ;
@property (nonatomic, assign) unsigned short time ;
@property (nonatomic, assign) char season ;
@property (nonatomic, assign) int64_t amount ;
@property (nonatomic, strong) NSMutableArray *mArray ;
@property (nonatomic, assign) BOOL isOk ;
@end
还有 copyWithZone:
- (id)copyWithZone:(NSZone *)zone
{
DataObject *obj = [[DataObject allocWithZone:zone] init] ;
unsigned int uVarCount = 0 ;
Ivar *pVarList = class_copyIvarList(self.class, &uVarCount) ;
for (unsigned int i = 0; i < uVarCount; ++i) {
Ivar *pVar = pVarList+i ;
const char *name = ivar_getName(*pVar) ;
const char *typeEncoding = ivar_getTypeEncoding(*pVar) ;
NSString *strTypeEncoding = [NSString stringWithUTF8String:typeEncoding] ;
if ([strTypeEncoding isEqualToString:@"[I10]"]) {
// its arrPrice, use memcpy to copy
memcpy(obj->arrPrice, self->arrPrice, sizeof(arrPrice)) ;
continue ;
} else if ([strTypeEncoding hasPrefix:@"@"]) {
// its a object
id o = object_getIvar(self, *pVar) ;
o = [o copy] ;
object_setIvar(obj, *pVar, o) ;
NSLog(@"var name:%s, type:%s, value:%@", name, typeEncoding, o) ;
} else {
// runtime error
id o = object_getIvar(self, *pVar) ;
object_setIvar(obj, *pVar, o) ;
}
}
free(pVarList) ;
return obj ;
}
当变量不是对象时出现运行时错误,我找到 why,但我不知道如何解决。
首先:在 OOP 中,复制是一个复杂的问题,通用的 90% 解决方案通常不是好主意。然而,...
如果你有一个非对象引用属性(NSInteger
、double
、struct
等),你必须使用-object_getInstanceVariable ()
.但事情可能更复杂。
让我们先试试看:
} else {
long *valuePointer;
object_getInstanceVariable(self, ivar_getName(pVar), & valuePointer) ;
object_setInstanceVariable(obj, ivar_getName(pVar), valuePointer) ;
}
如您所见,我使用了指向 long
的指针。这是合法的,如果 属性 类型是 long
。 (并且对于大小与 long
相同的所有其他类型可能是合法的。)关于其他类型,尤其是 struct
类型,你不能这样做,因为它们可以有不同的大小。因此,要获得完整的解决方案,您必须分析编码字符串。
至少在这一点上你应该重新考虑一下,通用副本是否是个好主意。我更喜欢 not 分支。
确实很难做出理想的解决方案,但我的目标是支持大多数标量值和 objective-c 对象。
主要思路是利用ivar_getOffset
获取ivar的位置,直接修改。为了实现这一点,我还需要知道 ivar 的大小,所以我创建了一个方法 - (unsigned int)sizeOfTypeEncoding:(NSString *)typeEncoding supportOrNot:(BOOL *)support
来计算 ivar 的大小和 return 它,如果类型 ivar 不支持,*support
将是 NO
并且会抛出异常。
因此,当我向 class 添加不受支持的 ivar 时,异常会告诉我像 arrPrice
一样手动执行此操作。
@interface DataObject : NSObject <NSCopying>
{
@public
unsigned int arrPrice[10];
}
@property (nonatomic, copy) NSString *code ;
@property (nonatomic, assign) unsigned char type ;
@property (nonatomic, assign) int digit ;
@property (nonatomic, assign) unsigned int count ;
@property (nonatomic, assign) unsigned short time ;
@property (nonatomic, assign) char season ;
@property (nonatomic, assign) int64_t amount ;
@property (nonatomic, strong) NSMutableArray *mArray ;
@property (nonatomic, assign) BOOL isOk ;
@end
@implementation DataObject
- (unsigned int)sizeOfTypeEncoding:(NSString *)typeEncoding supportOrNot:(BOOL *)support
{
*support = YES ;
unsigned int size = 0 ;
if ([typeEncoding isEqualToString:@"c"] ||
[typeEncoding isEqualToString:@"C"]) {
size = sizeof(char) ;
} else if ([typeEncoding isEqualToString:@"i"] ||
[typeEncoding isEqualToString:@"I"]) {
size = sizeof(int) ;
} else if ([typeEncoding isEqualToString:@"s"] ||
[typeEncoding isEqualToString:@"S"]) {
size = sizeof(short) ;
} else if ([typeEncoding isEqualToString:@"l"] ||
[typeEncoding isEqualToString:@"L"]) {
size = sizeof(long) ;
} else if ([typeEncoding isEqualToString:@"q"] ||
[typeEncoding isEqualToString:@"Q"]) {
size = sizeof(long long) ;
} else if ([typeEncoding isEqualToString:@"f"]) {
size = sizeof(float) ;
} else if ([typeEncoding isEqualToString:@"d"]) {
size = sizeof(double) ;
} else if ([typeEncoding isEqualToString:@"B"]) {
size = sizeof(bool) ;
} else {
*support = NO ;
// v is void
// * is char *
// @ is object
// # is class object
// : is method selector
// [ is array
// { is struct
// ( is union
// b is bit
// ^ pointer to type
// ? other
size = 0 ;
}
return size ;
}
- (id)copyWithZone:(NSZone *)zone
{
DataObject *obj = [[DataObject allocWithZone:zone] init] ;
unsigned int uVarCount = 0 ;
Ivar *pVarList = class_copyIvarList(self.class, &uVarCount) ;
for (unsigned int i = 0; i < uVarCount; ++i) {
Ivar *pVar = pVarList+i ;
const char *name = ivar_getName(*pVar) ;
const char *typeEncoding = ivar_getTypeEncoding(*pVar) ;
NSString *strTypeEncoding = [NSString stringWithUTF8String:typeEncoding] ;
NSLog(@"var name:%s, type:%s", name, typeEncoding) ;
if ([strTypeEncoding isEqualToString:@"[10I]"]) {
// it is arrPrice
memcpy(obj->arrPrice, self->arrPrice, sizeof(arrPrice)) ;
continue ;
}
if ([strTypeEncoding hasPrefix:@"@"]) {
// it is a object
id o = object_getIvar(self, *pVar) ;
o = [o copy] ;
object_setIvar(obj, *pVar, o) ;
} else {
unsigned int size = 0 ;
BOOL support = NO ;
size = [self sizeOfTypeEncoding:strTypeEncoding supportOrNot:&support] ;
if (!support) {
NSString *reason = [NSString stringWithFormat:@"don't support type encoding %@", strTypeEncoding] ;
NSException *exception = [NSException exceptionWithName:@"UnSupportTypeException" reason:reason userInfo:nil] ;
[exception raise] ;
}
ptrdiff_t offset = ivar_getOffset(*pVar) ;
uint8_t *src = (uint8_t *)(__bridge void *)self + offset ;
uint8_t *dst = (uint8_t *)(__bridge void *)obj + offset ;
memcpy(dst, src, size) ;
}
}
free(pVarList) ;
return obj ;
}
@end
在我的项目中有一个 class 有很多变量,现在我希望它符合 NSCopying
协议,所以我必须 "copy" [=14] 中的每个变量=].如果变量是一个对象,发送副本给它,如果它是一个标量值,只需使用赋值符号。
class可能会经常变化,这意味着我可能会在之后添加一些变量,然后我或其他人可能会忘记修改- (id)copyWithZone:(NSZone *)zone
方法中的代码。为了避免这种情况,我正在考虑使用 objective-c 的运行时功能来完成这项工作。
下面是Class的一部分,class中的变量是一个对象或标量值。你会注意到一个unsigned int数组,它是一个例外,你可以省略它,因为当变量的类型编码为[10I]
.
@interface DataObject : NSObject <NSCopying>
{
@public
unsigned int arrPrice[10];
}
@property (nonatomic, copy) NSString *code ;
@property (nonatomic, assign) unsigned char type ;
@property (nonatomic, assign) int digit ;
@property (nonatomic, assign) unsigned int count ;
@property (nonatomic, assign) unsigned short time ;
@property (nonatomic, assign) char season ;
@property (nonatomic, assign) int64_t amount ;
@property (nonatomic, strong) NSMutableArray *mArray ;
@property (nonatomic, assign) BOOL isOk ;
@end
还有 copyWithZone:
- (id)copyWithZone:(NSZone *)zone
{
DataObject *obj = [[DataObject allocWithZone:zone] init] ;
unsigned int uVarCount = 0 ;
Ivar *pVarList = class_copyIvarList(self.class, &uVarCount) ;
for (unsigned int i = 0; i < uVarCount; ++i) {
Ivar *pVar = pVarList+i ;
const char *name = ivar_getName(*pVar) ;
const char *typeEncoding = ivar_getTypeEncoding(*pVar) ;
NSString *strTypeEncoding = [NSString stringWithUTF8String:typeEncoding] ;
if ([strTypeEncoding isEqualToString:@"[I10]"]) {
// its arrPrice, use memcpy to copy
memcpy(obj->arrPrice, self->arrPrice, sizeof(arrPrice)) ;
continue ;
} else if ([strTypeEncoding hasPrefix:@"@"]) {
// its a object
id o = object_getIvar(self, *pVar) ;
o = [o copy] ;
object_setIvar(obj, *pVar, o) ;
NSLog(@"var name:%s, type:%s, value:%@", name, typeEncoding, o) ;
} else {
// runtime error
id o = object_getIvar(self, *pVar) ;
object_setIvar(obj, *pVar, o) ;
}
}
free(pVarList) ;
return obj ;
}
当变量不是对象时出现运行时错误,我找到 why,但我不知道如何解决。
首先:在 OOP 中,复制是一个复杂的问题,通用的 90% 解决方案通常不是好主意。然而,...
如果你有一个非对象引用属性(NSInteger
、double
、struct
等),你必须使用-object_getInstanceVariable ()
.但事情可能更复杂。
让我们先试试看:
} else {
long *valuePointer;
object_getInstanceVariable(self, ivar_getName(pVar), & valuePointer) ;
object_setInstanceVariable(obj, ivar_getName(pVar), valuePointer) ;
}
如您所见,我使用了指向 long
的指针。这是合法的,如果 属性 类型是 long
。 (并且对于大小与 long
相同的所有其他类型可能是合法的。)关于其他类型,尤其是 struct
类型,你不能这样做,因为它们可以有不同的大小。因此,要获得完整的解决方案,您必须分析编码字符串。
至少在这一点上你应该重新考虑一下,通用副本是否是个好主意。我更喜欢 not 分支。
确实很难做出理想的解决方案,但我的目标是支持大多数标量值和 objective-c 对象。
主要思路是利用ivar_getOffset
获取ivar的位置,直接修改。为了实现这一点,我还需要知道 ivar 的大小,所以我创建了一个方法 - (unsigned int)sizeOfTypeEncoding:(NSString *)typeEncoding supportOrNot:(BOOL *)support
来计算 ivar 的大小和 return 它,如果类型 ivar 不支持,*support
将是 NO
并且会抛出异常。
因此,当我向 class 添加不受支持的 ivar 时,异常会告诉我像 arrPrice
一样手动执行此操作。
@interface DataObject : NSObject <NSCopying>
{
@public
unsigned int arrPrice[10];
}
@property (nonatomic, copy) NSString *code ;
@property (nonatomic, assign) unsigned char type ;
@property (nonatomic, assign) int digit ;
@property (nonatomic, assign) unsigned int count ;
@property (nonatomic, assign) unsigned short time ;
@property (nonatomic, assign) char season ;
@property (nonatomic, assign) int64_t amount ;
@property (nonatomic, strong) NSMutableArray *mArray ;
@property (nonatomic, assign) BOOL isOk ;
@end
@implementation DataObject
- (unsigned int)sizeOfTypeEncoding:(NSString *)typeEncoding supportOrNot:(BOOL *)support
{
*support = YES ;
unsigned int size = 0 ;
if ([typeEncoding isEqualToString:@"c"] ||
[typeEncoding isEqualToString:@"C"]) {
size = sizeof(char) ;
} else if ([typeEncoding isEqualToString:@"i"] ||
[typeEncoding isEqualToString:@"I"]) {
size = sizeof(int) ;
} else if ([typeEncoding isEqualToString:@"s"] ||
[typeEncoding isEqualToString:@"S"]) {
size = sizeof(short) ;
} else if ([typeEncoding isEqualToString:@"l"] ||
[typeEncoding isEqualToString:@"L"]) {
size = sizeof(long) ;
} else if ([typeEncoding isEqualToString:@"q"] ||
[typeEncoding isEqualToString:@"Q"]) {
size = sizeof(long long) ;
} else if ([typeEncoding isEqualToString:@"f"]) {
size = sizeof(float) ;
} else if ([typeEncoding isEqualToString:@"d"]) {
size = sizeof(double) ;
} else if ([typeEncoding isEqualToString:@"B"]) {
size = sizeof(bool) ;
} else {
*support = NO ;
// v is void
// * is char *
// @ is object
// # is class object
// : is method selector
// [ is array
// { is struct
// ( is union
// b is bit
// ^ pointer to type
// ? other
size = 0 ;
}
return size ;
}
- (id)copyWithZone:(NSZone *)zone
{
DataObject *obj = [[DataObject allocWithZone:zone] init] ;
unsigned int uVarCount = 0 ;
Ivar *pVarList = class_copyIvarList(self.class, &uVarCount) ;
for (unsigned int i = 0; i < uVarCount; ++i) {
Ivar *pVar = pVarList+i ;
const char *name = ivar_getName(*pVar) ;
const char *typeEncoding = ivar_getTypeEncoding(*pVar) ;
NSString *strTypeEncoding = [NSString stringWithUTF8String:typeEncoding] ;
NSLog(@"var name:%s, type:%s", name, typeEncoding) ;
if ([strTypeEncoding isEqualToString:@"[10I]"]) {
// it is arrPrice
memcpy(obj->arrPrice, self->arrPrice, sizeof(arrPrice)) ;
continue ;
}
if ([strTypeEncoding hasPrefix:@"@"]) {
// it is a object
id o = object_getIvar(self, *pVar) ;
o = [o copy] ;
object_setIvar(obj, *pVar, o) ;
} else {
unsigned int size = 0 ;
BOOL support = NO ;
size = [self sizeOfTypeEncoding:strTypeEncoding supportOrNot:&support] ;
if (!support) {
NSString *reason = [NSString stringWithFormat:@"don't support type encoding %@", strTypeEncoding] ;
NSException *exception = [NSException exceptionWithName:@"UnSupportTypeException" reason:reason userInfo:nil] ;
[exception raise] ;
}
ptrdiff_t offset = ivar_getOffset(*pVar) ;
uint8_t *src = (uint8_t *)(__bridge void *)self + offset ;
uint8_t *dst = (uint8_t *)(__bridge void *)obj + offset ;
memcpy(dst, src, size) ;
}
}
free(pVarList) ;
return obj ;
}
@end