objective-c deep-copy

objective c - ¿Cómo puedo hacer una copia profunda en Objective-C?



deep-copy (6)

Estoy aprendiendo a desarrollar iOS y estoy confundido con copia profunda. Por ejemplo, tengo tres clases a continuación. Ahora quiero hacer una copia profunda de ClassA, ¿alguien puede enseñarme a terminar el método de copia?

UNA:

@interface ClassA : NSObject <NSCopying> @property (nonatomic, assign) int aInt; @property (nonatomic, retain) ClassB *bClass; @end

SEGUNDO:

@interface ClassB : NSObject <NSCopying> @property (nonatomic, assign) int bInt; @property (nonatomic, retain) ClassC *cClass; @end

DO:

@interface ClassC : NSObject <NSCopying> @property (nonatomic, assign) int cInt; @property (nonatomic, copy) NSString *str; @end


Debe agregar el método copyWithZone: en cada clase que desee copiar.

NB: escribí esto a mano, ten cuidado con los errores tipográficos.

-(id) copyWithZone:(NSZone *) zone { ClassA *object = [super copyWithZone:zone]; object.aInt = self.aInt; object.bClass = [self.bClass copyWithZone:zone]; return object; } -(id) copyWithZone:(NSZone *) zone { ClassB *object = [super copyWithZone:zone]; object.bInt = self.bInt; object.cClass = [self.cClass copyWithZone:zone]; return object; } -(id) copyWithZone:(NSZone *) zone { ClassC *object = [super copyWithZone:zone]; object.cInt = self.cInt; object.str = [self.str copy]; return object; }



Las especificaciones de Copyive y copyWithZone de Objective-C son falsas y peligrosas y no deben utilizarse. -! - Al menos no cuando se usa con ARC (conteo automático de referencias) (2016-08-23) -! - El código llevará a la escritura fuera de los límites de la memoria / desbordamientos de búfer. En su lugar, presento un método para copiar de forma segura los objetos initAsShallowCopy y deepCopy.

Ver los resultados de mi prueba en el código a continuación:

#import <Foundation/Foundation.h> @interface ClassA : NSObject { @public NSMutableString* A_Name; NSInteger A_NSInteger; long int A_int; float A_float; } -(id)init; -(id)copyWithZone:(NSZone *) zone; // DON''T USE copy OR copyWithZone, unless you ignore Apple''s guidelines and always make shallow copies in line with the correct example code here for initAsShallowCopy (but you return a copy instead of being a copy) -(id)initAsShallowCopy:(ClassA *)original; // Correct way to make a shallow copy -(void)deepCopy; // Correct way to make a deep copy (Call initAsShallowCopy first) @end @interface ClassB : ClassA { @public NSMutableString* B_Name; NSInteger B_NSInteger; long int B_int; float B_float; } -(id)init; -(id)copyWithZone:(NSZone *) zone; // DON''T USE copy OR copyWithZone, unless you ignore Apple''s guidelines and always make shallow copies in line with the correct example code here for initAsShallowCopy (but you return a copy instead of being a copy) -(id)initAsShallowCopy:(ClassB *)original; // Correct way to make a shallow copy -(void)deepCopy; // Correct way to make a deep copy (Call initAsShallowCopy first) -(void)print; @end @interface ClassCWithoutCopy : NSObject { @public NSMutableString* C_Name; NSInteger C_NSInteger; long int C_int; float C_float; } -(id)init; -(void)print; @end @implementation ClassA -(id)init { if ( self = [super init] ) { // initialize NSObject //A_Name = [[NSMutableString alloc] init]; //[A_Name setString:@"I am inited to A"]; A_Name = [NSMutableString stringWithString:@"I am inited to A"]; A_NSInteger = 1; A_int = 1; A_float = 1.0; return self; } return nil; } /* FROM https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/#//apple_ref/occ/instm/NSObject/copy -- NSObject Class Reference -- - (id)copy Discussion This is a convenience method for classes that adopt the NSCopying protocol. An exception is raised if there is no implementation for copyWithZone:. NSObject does not itself support the NSCopying protocol. Subclasses must support the protocol and implement the copyWithZone: method. A subclass version of the copyWithZone: method should send the message to super first, to incorporate its implementation, unless the subclass descends directly from NSObject. + copyWithZone: Discussion This method exists so class objects can be used in situations where you need an object that conforms to the NSCopying protocol. For example, this method lets you use a class object as a key to an NSDictionary object. You should not override this method. CONCLUSION copy says we should incorporate the implementation of copyWithZone, while copyWithZone says we should not override it.. So what is it? Looking at copyWithZone, we see that it is a class method (+), meaning it has not access to its instantiated members. So maybe they mean, we should not override the class method (+), but we should implement its instance method -copyWithZone: !!In any case we should not implement copy, because it is just made for convenience by Apple!! FROM: https://developer.apple.com/library/tvos/documentation/Cocoa/Reference/Foundation/Protocols/NSCopying_Protocol/index.html -- NSCopying -- Your options for implementing this protocol are as follows: 1) Implement NSCopying using alloc and init... in classes that don’t inherit copyWithZone:. 2) Implement NSCopying by invoking the superclass’s copyWithZone: when NSCopying behavior is inherited. If the superclass implementation might use the NSCopyObject function, make explicit assignments to pointer instance variables for retained objects. 3) Implement NSCopying by retaining the original instead of creating a new copy when the class and its contents are immutable. CONCLUSION: From 1) NSObject does not implement copyWithZone so any class that you make that should support copying should call [[Class alloc] init]. From 2) Any subclass of a copyable object should call [super copyWithZone:zone], but NOT [[Class alloc] init] !!!!!! */ -(id) copyWithZone:(NSZone *) zone { ClassA *CopiedObject = [[ClassA alloc] init]; if(CopiedObject){ CopiedObject->A_Name = [A_Name copy]; CopiedObject->A_NSInteger = A_NSInteger; CopiedObject->A_int = A_int; CopiedObject->A_float = A_float; return CopiedObject; } return nil; } -(id)initAsShallowCopy:(ClassA *)original // Correct way to make a shallow copy { /* Why this has to be done like this: It is very annoying to assign every variable explicitely. However this has to be done, in order for ARC (Automatic Reference Counting) (2016-08-23) to work. The compiler needs to be aware of any reference made to an object or reference cleared to an object in order to keep track of the reference counts. The danger is that when you add a variable to you class later on, you must not forget to update your initAsShallowCopy function and possibly your DeepCopy function. It would be much nicer if you could just do: *self = *original; But that gives compiler error: /DeepCopyTest/main.m:135:9: Cannot assign to class object (''ClassA'' invalid) So therefore there is also no raw memory copy between objects, so we are stuck with writing out each member variable explicitely. */ if ( self = [super init] ) { // initialize NSObject A_Name = original->A_Name; A_NSInteger = original->A_NSInteger; A_int = original->A_int; A_float = original->A_float; return self; } return nil; } -(void)deepCopy; // Correct way to make a deep copy (Call initAsShallowCopy first) { /* Luckily now, we only have to duplicate the objects that require a deep copy. So we don''t have to write out all the floats, ints and NSIntegers, etcetera. Thus only the pointers (*) to objects. */ A_Name = [A_Name copy]; } @end @implementation ClassB -(id)init { if ( self = [super init] ) { // initialize ClassA B_Name = [NSMutableString stringWithString:@"I am inited to B"]; B_NSInteger = 2; B_int = 2; B_float = 2.0; return self; } return nil; } -(id) copyWithZone:(NSZone *) zone { //ClassA *CopiedObject = [[ClassA alloc] init]; We are not a direct descendant from NSObject, so don''t call alloc-init // instead call the super copyWithZone ClassB *CopiedObject = [super copyWithZone:zone]; /* Using ARC (Automatic Reference Counting) 2016-08-23: THIS IS A MASSIVE BUFFER OVERFLOW/WRITING OUT OF BOUNDS RISK: Since super now allocates the object, it will now only allocate an object of size ClassA and effectively allocate too little memory for the ClassB. Unless memory allocation is upgraded to work with magic for Objective-C, DON''T USE copy or copyWithZone!!!! */ if(CopiedObject){ CopiedObject->B_Name = [B_Name copy]; CopiedObject->B_NSInteger = B_NSInteger; CopiedObject->B_int = B_int; CopiedObject->B_float = B_float; return CopiedObject; } return nil; } -(id)initAsShallowCopy:(ClassB *)original // Correct way to make a shallow copy { if ( self = [super initAsShallowCopy:original] ) { // initialize ClassA B_Name = original->B_Name; B_NSInteger = original->B_NSInteger; B_int = original->B_int; B_float = original->B_float; return self; } return nil; } -(void)deepCopy; // Correct way to make a deep copy (Call initAsShallowCopy first) { /* Luckily now, we only have to duplicate the objects that require a deep copy. So we don''t have to write out all the floats, ints and NSIntegers, etcetera. Thus only the pointers (*) to objects. */ [super deepCopy]; B_Name = [B_Name copy]; } -(void)print { NSLog(@"A_Name=/"%@/", A_NSInteger=%ld,A_int=%ld,A_float=%f",A_Name,A_NSInteger,A_int,A_float); NSLog(@"B_Name=/"%@/", B_NSInteger=%ld,B_int=%ld,B_float=%f",B_Name,B_NSInteger,B_int,B_float); } @end @implementation ClassCWithoutCopy -(id)init { if ( self = [super init] ) { // initialize NSObject C_Name = [NSMutableString stringWithString:@"I am inited to C"]; C_NSInteger = 3; C_int = 3; C_float = 3.0; return self; } return nil; } -(void)print { NSLog(@"C_Name=/"%@/", C_NSInteger=%ld,C_int=%ld,C_float=%f",C_Name,C_NSInteger,C_int,C_float); } @end int main(int argc, const char * argv[]) { @autoreleasepool { ClassB *OriginalB; ClassB *CopiedB; #define USE_CORRECT_DEEP_COPY_AND_SHALLOW_COPY 1 #define USE_CLASSC_WITHOUT_COPY_TEST 0 #if(USE_CLASSC_WITHOUT_COPY_TEST) ClassCWithoutCopy *OriginalC; ClassCWithoutCopy *CopiedC; OriginalC = [[ClassCWithoutCopy alloc] init]; CopiedC = [OriginalC copy]; /* Thread 1: signal SIGABRT: libc++abi.dylib: terminating with uncaught exception of type NSException *** Terminating app due to uncaught exception ''NSInvalidArgumentException'', reason: ''-[ClassCWithoutCopy copyWithZone:]: unrecognized selector sent to instance 0x100100450'' */ //CopiedC = [ClassCWithoutCopy copyWithZone:nil]; /* DeepCopyTest/main.m:283:33: ''copyWithZone:'' is unavailable: not available in automatic reference counting mode */ NSLog(@"OriginalC print:1"); [OriginalC print]; NSLog(@"CopiedC print:1"); [CopiedC print]; [OriginalC->C_Name appendString:@" and Appended as the original"]; OriginalC->C_NSInteger = 30; OriginalC->C_int = 30; OriginalC->C_float = 30.0; NSLog(@"OriginalC print:2"); [OriginalC print]; NSLog(@"CopiedC print:2"); [CopiedC print]; #endif #if(USE_CORRECT_DEEP_COPY_AND_SHALLOW_COPY) OriginalB = [[ClassB alloc] init]; CopiedB = [[ClassB alloc] initAsShallowCopy:OriginalB]; NSLog(@"OriginalB print:1"); [OriginalB print]; NSLog(@"CopiedB print:1"); [CopiedB print]; [OriginalB->A_Name appendString:@" and Appended as the original"]; OriginalB->A_NSInteger = 10; OriginalB->A_int = 10; OriginalB->A_float = 10.0; [OriginalB->B_Name appendString:@" and Appended as the original"]; OriginalB->B_NSInteger = 20; OriginalB->B_int = 20; OriginalB->B_float = 20.0; NSLog(@"OriginalB print:2"); [OriginalB print]; NSLog(@"CopiedB print:2"); [CopiedB print]; // This works as expected: The values of OriginalB and CopiedB differ, but the shallow copied strings are the same. // Now make a deep copy of CopiedB [CopiedB deepCopy]; [OriginalB->A_Name appendString:@" and Appended twice as the original"]; OriginalB->A_NSInteger = 100; OriginalB->A_int = 100; OriginalB->A_float = 100.0; [OriginalB->B_Name appendString:@" and Appended twice as the original"]; OriginalB->B_NSInteger = 200; OriginalB->B_int = 200; OriginalB->B_float = 200.0; NSLog(@"OriginalB print:3"); [OriginalB print]; NSLog(@"CopiedB print:3"); [CopiedB print]; // This works as expected: The values of OriginalB and CopiedB differ and als the deep copied strings are different. #else OriginalB = [[ClassB alloc] init]; CopiedB = [OriginalB copy]; // Undefined behaviour. You will write unallocated memory NSLog(@"OriginalB print:1"); [OriginalB print]; NSLog(@"CopiedB print:1"); /*[CopiedB print]; / * Thread 1: signal SIGABRT: libc++abi.dylib: terminating with uncaught exception of type NSException *** Terminating app due to uncaught exception ''NSInvalidArgumentException'', reason: ''-[ClassA print]: unrecognized selector sent to instance 0x10010ad60'' */ NSLog(@"A_Name=/"%@/", A_NSInteger=%ld,A_int=%ld,A_float=%f",CopiedB->A_Name,CopiedB->A_NSInteger,CopiedB->A_int,CopiedB->A_float); NSLog(@"B_Name=/"%@/", B_NSInteger=%ld,B_int=%ld,B_float=%f",CopiedB->B_Name,CopiedB->B_NSInteger,CopiedB->B_int,CopiedB->B_float); // Undefined behaviour. You will read unallocated memory [OriginalB->A_Name appendString:@" and Appended as the original"]; OriginalB->A_NSInteger = 10; OriginalB->A_int = 10; OriginalB->A_float = 10.0; [OriginalB->B_Name appendString:@" and Appended as the original"]; OriginalB->B_NSInteger = 20; OriginalB->B_int = 20; OriginalB->B_float = 20.0; // This at least works: Changing Original, does not alter the values of Copy. NSLog(@"OriginalB print:2"); [OriginalB print]; NSLog(@"CopiedB print:2"); NSLog(@"A_Name=/"%@/", A_NSInteger=%ld,A_int=%ld,A_float=%f",CopiedB->A_Name,CopiedB->A_NSInteger,CopiedB->A_int,CopiedB->A_float); //NSLog(@"B_Name=/"%@/", B_NSInteger=%ld,B_int=%ld,B_float=%f",CopiedB->B_Name,CopiedB->B_NSInteger,CopiedB->B_int,CopiedB->B_float); // Undefined behaviour. You will read unallocated memory /*[CopiedB->A_Name appendString:@" and Appended as the copy"]; / * Thread 1: signal SIGABRT: libc++abi.dylib: terminating with uncaught exception of type NSException *** Terminating app due to uncaught exception ''NSInvalidArgumentException'', reason: ''Attempt to mutate immutable object with appendString:'' */ CopiedB->A_NSInteger = 100; CopiedB->A_int = 100; CopiedB->A_float = 100.0; /*[CopiedB->B_Name appendString:@" and Appended as the copy"]; / * Thread 1: signal SIGABRT: libc++abi.dylib: terminating with uncaught exception of type NSException *** Terminating app due to uncaught exception ''NSInvalidArgumentException'', reason: ''Attempt to mutate immutable object with appendString:''*/ CopiedB->B_NSInteger = 200; // Undefined behaviour. You will write unallocated memory CopiedB->B_int = 200; // Undefined behaviour. You will write unallocated memory CopiedB->B_float = 200.0; // Undefined behaviour. You will write unallocated memory /* Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) DeepCopyTest(2376,0x7fff7edda310) malloc: *** error for object 0x10010ad98: incorrect checksum for freed object - object was probably modified after being freed. *** set a breakpoint in malloc_error_break to debug */ NSLog(@"OriginalB print after modification of CopiedB:"); [OriginalB print]; NSLog(@"CopiedB print after modification of CopiedB:"); /*[CopiedB print];; / * Thread 1: signal SIGABRT: libc++abi.dylib: terminating with uncaught exception of type NSException *** Terminating app due to uncaught exception ''NSInvalidArgumentException'', reason: ''-[ClassA print]: unrecognized selector sent to instance 0x10010ad60'' */ #endif } return 0; }

PS-1: DESDE:

https://developer.apple.com/library/mac/documentation/General/Conceptual/DevPedia-CocoaCore/ObjectCopying.html

- Copia de objetos -

Una copia profunda duplica los objetos a los que se hace referencia, mientras que una copia superficial duplica solo las referencias a esos objetos. Por lo tanto, si el objeto A se copia de forma superficial al objeto B, el objeto B se refiere a la misma variable de instancia (o propiedad) a la que se refiere el objeto A La copia profunda de objetos es preferible a la copia superficial, especialmente con objetos de valor.

NOTA:

Esta es una formulación poco clara, especialmente con la ilustración acompañada, que sugiere una explicación incorrecta.

Esta formulación hace que parezca que dos referencias al mismo objeto cuentan como una copia superficial. Esto no es verdad. No es una copia en absoluto.

La formulación clara sería que: -Una copia superficial de un objeto tiene todos los valores y referencias copiados de su padre, pero en sí mismo es un objeto único en la memoria. -Una copia profunda de un objeto tiene todos los valores copiados de su padre y es un objeto único en la memoria, pero todas las referencias ahora hacen referencia a -deep ellos mismos- copias de los objetos de referencias originales.

Aunque la implementación exacta de la copia profunda no puede dar copias profundas al 100% Los objetos que apuntan a referencias externas (como un elemento de hardware o un controlador de gráficos no se pueden duplicar, pero solo aumentan el recuento de referencias) Algunas copias en profundidad no tienen sentido funcional. Un objeto puede hacer referencia a la ventana en la que se encuentra, pero no tiene sentido duplicar la ventana. Un objeto también podría hacer referencia a datos que se consideran inmutables, por lo que no sería eficiente duplicarlos.

PS-2: Podrías haberme dado la sugerencia de ctrl-K antes de intentar formatear todo mi código manualmente.

PS-3: Apple-Z (deshacer) deshace todo mi formato en lugar del último y no puedo rehacerlo.


Objective-C en iOS no ofrece ningún lenguaje directo o construcción de biblioteca para cambiar entre una copia superficial y una copia profunda. Cada clase define lo que significa "obtener su copia":

@implementation ClassA - (id) copyWithZone: (NSZone*) zone { ClassA *copy = [super copyWithZone:zone]; [copy setBClass:bClass]; // this would be a shallow copy [copy setBClass:[bClass copy]]; // this would be a deep copy return copy; } @end

Por supuesto, tendrías que tomar la misma decisión en ClassB y ClassC. Si no me equivoco, la semántica habitual para una copia en Objective-C es devolver una copia superficial. Consulte también esta pregunta sobre cómo copiar arreglos para obtener más información sobre el tema.


Siguiendo la explicación en http://www.techotopia.com/index.php/Copying_Objects_in_Objective-C

"Esto se puede lograr escribiendo el objeto y sus elementos constitutivos en un archivo y luego volviendo a leer el nuevo objeto".

@implementation ClassA - (id)copyWithZone:(NSZone*)zone{ NSData *buffer; buffer = [NSKeyedArchiver archivedDataWithRootObject:self]; ClassA *copy = [NSKeyedUnarchiver unarchiveObjectWithData: buffer]; return copy; } @end


Tenía clases personalizadas con largas listas de propiedades, así que repetí sobre ellas:

@interface MyClass : NSObject <NSCopying> #import <objc/runtime.h> -(id) copyWithZone: (NSZone *) zone { MyClass *myCopy = [[MyClass alloc] init]; //deepCopy unsigned int numOfProperties; objc_property_t *properties = class_copyPropertyList([self class], &numOfProperties); for (int i = 0; i < numOfProperties; i++) { objc_property_t property = properties[i]; NSString *propertyName = [[NSString alloc]initWithCString:property_getName(property) encoding:NSUTF8StringEncoding]; [adressCopy setValue:[[self valueForKey:propertyName] copy] forKey:propertyName]; } return myCopy; }

Todas las customClassProperties necesitarán implementar esto también.