In certain contexts (for example, when hooking into internals for testing) it would be convenient to be able to create copies of instances of classes which do not adopt NSCopying
(do not implement -copyWithZone:
). How can this be achieved? (Note: it would not be enough to implement the protocol in a category because not all of a class' instance variables are visible in its header.)
I've tried iterating over the object's Ivar
s, and (1) for object types recursing (or retaining), and, (2) for primitive types, obtaining the address of the ivar in the original instanced and the copy that's being made and memcpy
ing the buffer starting at the source ivar's address to the destination ivar's address.
@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
This works somewhat, but there are a couple of glaring issues which I am aware of, not to mention those that I'm not aware of. First of all, it does not copy ivars inherited from superclasses. Secondly, it does not work for collections. In addition, because of a bug in NSGetSizeAndAlignment
, it does not work for bitmasks. Additionally, it fails to consider associated objects.
How can an instance of an object (and all the objects that it owns) be recursively copied? If this is not entirely possible (dealing with an owned object which is retained and stored in a void pointer seems rather difficult if even possible), to what extent can it be done?