Implementieren von NSCopying

83

Ich habe die NSCopyingDokumente gelesen , bin mir aber immer noch nicht sicher, wie ich die erforderlichen Maßnahmen umsetzen soll.

Meine Klasse 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

Die VendorKlasse verfügt über ein Array von Objekten, die aufgerufen werden Car.

Mein CarObjekt:

@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

So Vendorhält eine Reihe von CarObjekten. CarEnthält 2 Arrays anderer benutzerdefinierter Objekte.

Beide Vendorund Carsind init aus einem Wörterbuch. Ich werde eine dieser Methoden hinzufügen, sie können relevant sein oder auch nicht.

-(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;
}

Um das gruselige Problem zusammenzufassen.

Ich muss eine Reihe von VendorObjekten kopieren . Ich glaube, ich muss das NSCopyingProtokoll implementieren Vendor, was bedeuten kann, dass ich es auch implementieren muss, Carda es Vendorein Array von Cars enthält. Das heißt, ich muss es auch in den Klassen implementieren, die in den 2 zum CarObjekt gehörenden Arrays enthalten sind .

Ich würde es wirklich begrüßen, wenn ich eine Anleitung zur Implementierung des NSCopyingProtokolls erhalten könnte Vendor. Ich kann dazu nirgendwo Tutorials finden.

Mitalkumar Gamit
quelle
Haben Sie die Dokumentation von NSCopying gelesen? Ich fand es ziemlich klar, wenn es gebraucht wurde.
Jv42
4
Ja, lesen Sie und es liest es erneut. Ich finde Apple-Dokumente selten leicht zu erlernen, obwohl sie sich hervorragend zum Auffinden von Methoden usw. beim Programmieren eignen. Danke -Code

Antworten:

186

Um NSCopying zu implementieren , muss Ihr Objekt auf den -copyWithZone:Selektor reagieren . So erklären Sie, dass Sie sich daran anpassen:

@interface MyObject : NSObject <NSCopying> {

Dann in der Implementierung Ihres Objekts (Ihrer .mDatei):

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

Was soll Ihr Code tun? Erstellen Sie zunächst eine neue Instanz des Objekts. Sie können aufrufen [[[self class] alloc] init], um ein initialisiertes Objekt der aktuellen Klasse abzurufen, das sich gut für Unterklassen eignet. Anschließend können Sie für alle Instanzvariablen, die eine Unterklasse sind NSObject, die das Kopieren unterstützt, [thatObject copyWithZone:zone]das neue Objekt aufrufen . Für primitive Typen ( int, char, BOOLund Freunde) nur die Variablen gleich sein. Für Ihren obejct Vendor würde es also so aussehen:

- (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
quelle
2
@Code: copywird normalerweise als flache Kopie implementiert, wie Jeff gezeigt hat. Es ist ungewöhnlich - wenn auch nicht unvorstellbar -, dass Sie eine vollständige, tiefe Kopie wünschen (bei der alles bis zum Ende kopiert wird). Tiefe Kopien sind auch viel schwieriger, daher möchten Sie im Allgemeinen sicher sein, dass dies wirklich das ist, was Sie wollen.
Chuck
3
Es gibt ein Problem in Ihrem Code, bei dem Sie Ihre Unterklassen kopieren, da copyWithZone:ein Objekt mit dem Referenzzähler 1 zurückgegeben wird und keine automatische Freigabe erfolgt. Dies führt zu einem Leck. Sie müssen mindestens eine Autorelease hinzufügen.
Marius
22
Sollte nicht [[self class] alloc]verwenden allocWithZonestattdessen? Tut mir leid, dass ich das angesprochen habe.
Jweyrich
1
Leute, ich nehme an, wenn Sie ARC verwenden (da das minimal unterstützte IOS für jede App 4.3 ist), müssen Sie sich keine Gedanken über die Freigabe und automatische Freigabe machen.
Rishabh
1
@GeneralMike: Dies sollte wahrscheinlich eine separate Frage sein, aber im Allgemeinen (siehe, was ich dort gemacht habe?) Möchten Sie sicherstellen, dass jedes Objekt während einer tiefen Kopie aus dem Original kopiert wird - und sicherstellen, dass ihre -copyMethoden auch tiefe Kopien erstellen .
Jeff Kelley
6

Diese Antwort ähnelt der akzeptierten, verwendet allocWithZone:jedoch ARC und wird für ARC aktualisiert. NSZone ist eine Grundklasse für die Zuweisung von Speicher. Während das Ignorieren NSZonein den meisten Fällen funktioniert, ist es immer noch falsch.

Um korrekt zu implementieren NSCopying, müssen Sie eine Protokollmethode implementieren, die eine neue Kopie des Objekts mit Eigenschaften zuweist, die den Werten des Originals entsprechen.

Geben Sie in der Schnittstellendeklaration im Header an, dass Ihre Klasse das NSCopyingProtokoll implementiert :

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

Fügen Sie in der .m-Implementierung eine -(id)copyWithZoneMethode hinzu, die ungefähr so aussieht:

- (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
quelle
2

Schnelle Version

Rufen Sie einfach object.copy()an, um die Kopie zu erstellen.

Ich habe es nicht copy()für Werttypen verwendet, da diese "automatisch" kopiert werden. Aber ich musste copy()für classTypen verwenden.

Ich habe den NSZoneParameter ignoriert , weil Dokumente sagen, dass er veraltet ist:

Dieser Parameter wird ignoriert. Speicherzonen werden von Objective-C nicht mehr verwendet.

Bitte beachten Sie auch, dass dies eine vereinfachte Implementierung ist. Wenn Sie Unterklassen haben, wird es etwas kniffliger und Sie sollten den dynamischen Typ verwenden : 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
quelle