Implementación de NSCopying

84

He leído el NSCopying documentos, pero todavía no estoy seguro de cómo implementar lo que se requiere.

Mi clase Vendor:

@interface Vendor : NSObject 
{
    NSString        *vendorID;
    NSMutableArray  *availableCars;
    BOOL            atAirport;
}

@property (nonatomic, copy) NSString *vendorID;
@property (nonatomic, retain) NSMutableArray *availableCars;
@property (nonatomic, assign) BOOL atAirport;

- (id)initFromVehVendorAvailsDictionary:(NSDictionary *)vehVendorAvails;

@end

los Vendor clase tiene una matriz de objetos llamados Car.

Mi Carobjeto:

@interface Car : NSObject 
{
    BOOL            isAvailable;
    NSString        *transmissionType;
    NSMutableArray  *vehicleCharges; 
    NSMutableArray  *fees; 
}

@property (nonatomic, assign) BOOL isAvailable;
@property (nonatomic, copy) NSString *transmissionType;
@property (nonatomic, retain) NSMutableArray *vehicleCharges;
@property (nonatomic, retain) NSMutableArray *fees;

- (id) initFromVehicleDictionary:(NSDictionary *)vehicleDictionary;

@end

Entonces, Vendorcontiene una matriz deCar objetos. Carcontiene 2 matrices de otros objetos personalizados.

Ambos Vendory Carson iniciales de un diccionario. Agregaré uno de estos métodos, pueden ser relevantes o no.

-(id)initFromVehVendorAvailsDictionary:(NSDictionary *)vehVendorAvails {

    self.vendorCode      = [[vehVendorAvails objectForKey:@"Vendor"] 
                           objectForKey:@"@Code"];

    self.vendorName      = [[vehVendorAvails objectForKey:@"Vendor"] 
                           objectForKey:@"@CompanyShortName"];

    self.vendorDivision  = [[vehVendorAvails objectForKey:@"Vendor"]   
                           objectForKey:@"@Division"];

    self.locationCode    = [[[vehVendorAvails objectForKey:@"Info"] 
                           objectForKey:@"LocationDetails"] 
                           objectForKey:@"@Code"];

    self.atAirport       = [[[[vehVendorAvails objectForKey:@"Info"] 
                           objectForKey:@"LocationDetails"] 
                           objectForKey:@"@AtAirport"] boolValue];

    self.venLocationName = [[[vehVendorAvails objectForKey:@"Info"] 
                           objectForKey:@"LocationDetails"] 
                           objectForKey:@"@Name"];

    self.venAddress      = [[[[vehVendorAvails objectForKey:@"Info"] 
                           objectForKey:@"LocationDetails"] 
                           objectForKey:@"Address"] 
                           objectForKey:@"AddressLine"];

    self.venCountryCode  = [[[[[vehVendorAvails objectForKey:@"Info"]  
                           objectForKey:@"LocationDetails"] 
                           objectForKey:@"Address"] 
                           objectForKey:@"CountryName"]
                           objectForKey:@"@Code"];

    self.venPhone        = [[[[vehVendorAvails objectForKey:@"Info"]  
                           objectForKey:@"LocationDetails"]        
                           objectForKey:@"Telephone"] 
                           objectForKey:@"@PhoneNumber"];

    availableCars        = [[NSMutableArray alloc] init];

    NSMutableArray *cars = (NSMutableArray *)[vehVendorAvails objectForKey:@"VehAvails"];

    for (int i = 0; i < [cars count]; i++) {

        Car *car = [[Car alloc] initFromVehicleDictionary:[cars objectAtIndex:i]];
        [availableCars addObject:car];
        [car release];
    }

    self.venLogo = [[[vehVendorAvails objectForKey:@"Info"] 
                   objectForKey:@"TPA_Extensions"] 
                   objectForKey:@"VendorPictureURL"];

    return self;
}

Entonces, para resumir el aterrador problema.

Necesito copiar una serie de Vendorobjetos. Creo que necesito implementar el NSCopyingprotocolo Vendor, lo que puede significar que debo implementarlo también, Carya que Vendorcontiene una matriz de Cars. Eso significa que también necesito implementarlo en las clases que se encuentran en las 2 matrices que pertenecen al Carobjeto.

Realmente agradecería si pudiera obtener alguna orientación sobre la implementación del NSCopyingprotocolo Vendor, no puedo encontrar tutoriales sobre esto en ningún lado.

Mitalkumar Gamit
fuente
¿Ha leído la documentación de NSCopying? Lo encontré bastante claro cuando lo necesitaba.
jv42
4
Sí, léelo y vuelva a leerlo. Rara vez encuentro los documentos de Apple fáciles de aprender, aunque son excelentes para encontrar métodos, etc., durante la programación. Gracias -Código

Respuestas:

186

Para implementar NSCopying , su objeto debe responder al -copyWithZone:selector. Así es como declara que lo cumple:

@interface MyObject : NSObject <NSCopying> {

Luego, en la implementación de su objeto (su .marchivo):

- (id)copyWithZone:(NSZone *)zone
{
    // Copying code here.
}

¿Qué debería hacer tu código? Primero, cree una nueva instancia del objeto; puede llamar [[[self class] alloc] init]para obtener un objeto inicializado de la clase actual, que funciona bien para subclases. Luego, para cualquier variable de instancia que sea una subclase de NSObjectque admita la copia, puede solicitar [thatObject copyWithZone:zone]el nuevo objeto. Para los tipos primitivos ( int, char, BOOLy amigos) acaba de establecer las variables a ser igual. Por lo tanto, para su proveedor concreto, se vería así:

- (id)copyWithZone:(NSZone *)zone
{
    id copy = [[[self class] alloc] init];

    if (copy) {
        // Copy NSObject subclasses
        [copy setVendorID:[[self.vendorID copyWithZone:zone] autorelease]];
        [copy setAvailableCars:[[self.availableCars copyWithZone:zone] autorelease]];

        // Set primitives
        [copy setAtAirport:self.atAirport];
    }

    return copy;
}
Jeff Kelley
fuente
2
@Code: copynormalmente se implementa como una copia superficial como mostró Jeff. Es inusual, aunque no inconcebible, que desee una copia en profundidad completa (donde se copia todo el camino hacia abajo). Las copias profundas también son mucho más problemáticas, por lo que generalmente desea estar seguro de que eso es realmente lo que desea.
Chuck
3
Hay un problema en su código donde copia sus subclases, ya que copyWithZone:devuelve un objeto con un recuento de referencia de 1 y sin liberación automática, esto causará una fuga. Debe agregar al menos una liberación automática.
Marius
22
¿No debería [[self class] alloc]usar allocWithZoneen su lugar? Perdón por mencionar esto.
jweyrich
1
Amigos, supongo que al usar ARC (ya que el IOS mínimo admitido para cualquier aplicación es 4.3), no deben preocuparse por el lanzamiento y el lanzamiento automático.
rishabh
1
@GeneralMike: Esta probablemente debería ser una pregunta separada, pero en general (¿ves lo que hice allí?), Debes asegurarte de copiar todos los objetos del original durante una copia profunda, y asegurarte de que sus -copymétodos también hagan copias profundas. .
Jeff Kelley
6

Esta respuesta es similar a la aceptada, pero usa allocWithZone:y está actualizada para ARC. NSZone es una clase básica para asignar memoria. Mientras ignoraNSZone puede funcionar en la mayoría de los casos, sigue siendo incorrecto.

Implementar correctamente NSCopying , debe implementar un método de protocolo que asigne una nueva copia del objeto, con propiedades que coincidan con los valores del original.

En la declaración de interfaz en el encabezado, especifique que su clase implementa el NSCopyingprotocolo:

@interface Car : NSObject<NSCopying>
{
 ...
}

En la implementación .m agregue un -(id)copyWithZonemétodo que se parece a lo siguiente:

- (id)copyWithZone:(NSZone*)zone
{
    Car* carCopy = [[[self class] allocWithZone:zone] init];

    if (carCopy)
    {
        carCopy.isAvailable = _isAvailable;
        carCopy.transmissionType = _transmissionType;
        ... // assign all other properties.
    }

    return carCopy;
}
Justin Meiners
fuente
2

Versión rápida

Simplemente llame object.copy()para crear la copia.

No utilicé copy()para tipos de valor ya que se copian "automáticamente". Pero tuve que usar copy()para classtipos.

Ignoré el NSZoneparámetro porque los documentos dicen que está obsoleto:

Este parámetro se ignora. Objective-C ya no utiliza las zonas de memoria.

Además, tenga en cuenta que se trata de una implementación simplificada. Si tiene subclases se pone un poco Tricker y se debe utilizar tipo dinámico: type(of: self).init(transmissionType: transmissionType).

class Vendor {
    let vendorId: String
    var availableCars: [Car] = []

    init(vendorId: String) {
        self.vendorId = vendorId
    }
}

extension Vendor: NSCopying {
    func copy(with zone: NSZone? = nil) -> Any {
        let copy = Vendor(vendorId: vendorId)
        if let availableCarsCopy = availableCars.map({$0.copy()}) as? [Car] {
            copy.availableCars = availableCarsCopy
        }
        return copy
    }
}

class Car {
    let transmissionType: String
    var isAvailable: Bool = false
    var fees: [Double] = []

    init(transmissionType: String) {
        self.transmissionType = transmissionType
    }
}

extension Car: NSCopying {
    func copy(with zone: NSZone? = nil) -> Any {
        let copy = Car(transmissionType: transmissionType)
        copy.isAvailable = isAvailable
        copy.fees = fees
        return copy
    }
}
kgaidis
fuente