如何为不采用NSCopying的类创建对象实例的副本?

时间:2013-04-05 03:11:55

标签: objective-c objective-c-runtime

在某些上下文中(例如,当挂钩进入内部进行测试时)能够创建不采用NSCopying的类实例的副本(不实现-copyWithZone:)会很方便。怎么能实现这一目标? (注意:在类别中实现协议是不够的,因为并非所有类的实例变量都在其标题中可见。)

我尝试迭代对象的Ivar,以及(1)对象类型递归(或保留),以及(2)对于基本类型,获取原始实例中的ivar的地址以及从源ivar的地址开始的memcpy缓冲区到目的地ivar的地址。

@interface NSObject (ADDLCopy)

- (id)addl_generateCopyDeep:(BOOL)deepCopy;

@end

@implementation NSObject (ADDLCopy)

//modified from http://stackoverflow.com/a/12265664/1052673
- (void *)addl_ivarPointerForName:(const char *)name
{
    void *res = NULL;

    Ivar ivar = class_getInstanceVariable(self.class, name);

    if (ivar) {
        res = (void *)self + ivar_getOffset(ivar);
    }

    return res;
}


- (id)addl_generateCopyDeep:(BOOL)deepCopy;
{
    id res = [[self.class alloc] init];

    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList(self.class, &count);
    for (unsigned int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        //We need to try here because of a bug with NSGetSizeAndAlignment
        //which prevents bitfields from being handled properly and results
        //in an exception being thrown.  See this link for more discussion:
        //http://lists.apple.com/archives/cocoa-dev/2008/Sep/msg00883.html
        @try {
            NSUInteger size = 0;
            const char *encoding = ivar_getTypeEncoding(ivar);
            NSGetSizeAndAlignment(encoding, &size, NULL);
            char firstEncodingCharacter[2];
            strncpy(firstEncodingCharacter, encoding, 1);
            firstEncodingCharacter[1] = 0;
            if (strcmp(firstEncodingCharacter, "@") == 0) {
                if (deepCopy) {
                    id original = object_getIvar(self, ivar);
                    id copy = [original addl_generateCopyDeep:deepCopy];
                    object_setIvar(res, ivar, copy);
                } else {
                    id original = object_getIvar(self, ivar);
                    object_setIvar(res, ivar, original);
                }
            } else {
                const char *name = ivar_getName(ivar);
                void *bytesSource = [self addl_ivarPointerForName:name];
                void *bytesTarget = [res addl_ivarPointerForName:name];
                memcpy(bytesTarget, bytesSource, size);
            }
        }
        @catch (NSException *exception) {}
        @finally {}
    }
    free(ivars);

    return res;
}

@end

这有些作用,但我知道有几个明显的问题,更不用说那些我不知道的问题了。首先,它不会复制从超类继承的ivars。其次,它不适用于收藏。另外,由于a bug in NSGetSizeAndAlignment,它不适用于位掩码。此外,它无法考虑关联对象。

如何递归复制对象的实例(以及它拥有的所有对象)?如果这不是完全可能的(如果可能的话,处理保留并存储在void指针中的拥有对象似乎相当困难),它可以在多大程度上完成?

1 个答案:

答案 0 :(得分:8)

简而言之,你不能。

“深度”复制是一个极其特定于域的问题。

例如,您是复制对象的delegate还是将副本的delegate指向同一对象(或者将其设置为nil并要求复制客户端为设置它?)

对于这个例子,与复制相关的每个上下文业务逻辑还有大量其他一次性位(这就是为什么集合类上没有实现“深度复制”的概念)。

以上所有假设实际上都有实现细节。对于在框架中实现的类,没有办法安全地复制它们,因为根本没有可用的数据甚至不知道需要复制哪些内存位,更不用说如何了。

您需要限制复制操作的范围和定义,然后寻求解决方案。