3

In my project there is a class which has many variables, now I want it to conform to NSCopying protocol, so I have to "copy" every variable in - (id)copyWithZone:(NSZone *)zone. If the variable is a object, send copy to it, if it is a scalar value, just use assignment symbol.

The class may change frequently, which means I may add some variables afterwards, then I or somebody else may forget to modify the code in - (id)copyWithZone:(NSZone *)zone method. To avoid it, I'm thinking about to use objective-c's runtime feature to do the work.

Below is a part of the Class, the variables in the class are ether a object or a scalar value. You will notice an array of unsigned int, it is an exception, you can omit it because I can manually copy it when the type encoding of variable is [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

And the 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 ;
}

I get runtime error when the variable is not an object and I find why, but I don't know how to solve it.

Community
  • 1
  • 1
KudoCC
  • 6,912
  • 1
  • 24
  • 53

2 Answers2

0

First of all: In OOP copying is a complex problem and generic 90 % solutions are usually no good idea. However, …

If you have a non-object reference property (NSInteger, double, struct and so on), you have to use -object_getInstanceVariable (). But things can be more complicated.

Let's have a first try:

   } else {
        long *valuePointer;
        object_getInstanceVariable(self, ivar_getName(pVar), & valuePointer) ;
        object_setInstanceVariable(obj, ivar_getName(pVar), valuePointer) ;
    }

As you can see, I use a pointer to long. This is legal, if the property type is long. (And might be legal for every other type with the same size as long.) Concerning other types, especially struct types, you cannot do that, because they can have a different size. So to have a complete solution you have to analyze the encoding string.

At least at this point you should think again, whether a generic copy is a good idea or not. I prefer the not branch.

Amin Negm-Awad
  • 16,582
  • 3
  • 35
  • 50
  • Thanks for your answer. I don't mean to find an ideal solution and I get a solution to meet my case. By the way `object_getInstanceVariable` can't work in ARC. – KudoCC Jun 03 '15 at 01:48
  • Ah, yes, I remember that. Than you can read the value of the ivar via its offset (`ivar_getOffset()`). – Amin Negm-Awad Jun 03 '15 at 04:50
0

It's true that it's hard to make a ideal solution, but my goal is to support most scalar values and objective-c object.

The main idea is to use ivar_getOffset to get the position of ivar and modify it directly. To achieve that I also need to know the size of the ivar, so I create a method - (unsigned int)sizeOfTypeEncoding:(NSString *)typeEncoding supportOrNot:(BOOL *)support which calculate the size of ivar and return it, if the type ivar is not support, *support would be NO and an Exception would be thrown.

So when I add an unsupported ivar to the class, an exception could tell me to do it manually like 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
KudoCC
  • 6,912
  • 1
  • 24
  • 53