Wdrażanie NSCopying

84

Przeczytałem NSCopyingdokumenty, ale wciąż nie jestem pewien, jak wdrożyć to, co jest wymagane.

Moja klasa 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

VendorKlasa ma tablicę obiektów zwanych Car.

Mój Carobiekt:

@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

Więc Vendorprzechowuje tablicę Carobiektów. Carzawiera 2 tablice innych obiektów niestandardowych.

Obie Vendori Carsą inicjowane ze słownika. Dodam jedną z tych metod, mogą być istotne lub nie.

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

Podsumowując przerażający problem.

Muszę skopiować tablicę Vendorobiektów. Uważam, że muszę zaimplementować NSCopyingprotokół Vendor, co może oznaczać, że muszę go również zaimplementować, Carponieważ Vendorzawiera tablicę Cars. Oznacza to, że muszę go również zaimplementować na klasach, które są przechowywane w 2 tablicach należących do Carobiektu.

Naprawdę byłbym wdzięczny, gdybym mógł uzyskać wskazówki dotyczące implementacji NSCopyingprotokołu Vendor, nigdzie nie mogę znaleźć żadnych tutoriali na ten temat.

Mitalkumar Gamit
źródło
Czy przeczytałeś dokumentację NSCopying? W razie potrzeby okazało się to całkiem jasne.
jv42
4
Tak, przeczytaj i przeczytaj ponownie. Rzadko wydaje mi się, że dokumenty firmy Apple są łatwe do nauczenia, chociaż świetnie nadają się do znajdowania metod itp. Podczas programowania. Dzięki -Kod

Odpowiedzi:

186

Aby zaimplementować NSCopying , obiekt musi odpowiadać na -copyWithZone:selektor. Oto jak deklarujesz, że się do tego dostosujesz:

@interface MyObject : NSObject <NSCopying> {

Następnie w implementacji twojego obiektu (twój .mplik):

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

Co powinien zrobić twój kod? Najpierw utwórz nową instancję obiektu - możesz wywołać [[[self class] alloc] init]zainicjowany obiekt bieżącej klasy, który dobrze sprawdza się w przypadku tworzenia podklas. Następnie dla każdej zmiennej instancji, która jest podklasą NSObjectobsługującą kopiowanie, można wywołać [thatObject copyWithZone:zone]nowy obiekt. Dla typów pierwotnych ( int, char, BOOLi przyjaciele) wystarczy ustawić zmienne być równe. Więc dla twojego dostawcy przedmiotu wyglądałoby to tak:

- (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
źródło
2
@Code: copyjest zazwyczaj implementowany jako płytka kopia, jak pokazał Jeff. To niezwykłe - choć nie do pomyślenia - że chciałbyś mieć pełną, głęboką kopię (w której wszystko jest kopiowane). Głębokie kopie też są o wiele bardziej kłopotliwe, więc generalnie chcesz mieć pewność, że naprawdę tego chcesz.
Chuck
3
W Twoim kodzie występuje problem polegający na tym, że kopiujesz podklasy, ponieważ copyWithZone:zwraca obiekt z liczbą odwołań równą 1 i bez autorelease spowoduje to wyciek. Musisz dodać przynajmniej autorelease.
Marius
22
Nie należy [[self class] alloc]używać allocWithZonezamiast tego? Przepraszam, że o tym wspominam.
jweyrich
1
Ludzie, przypuszczam, że używając ARC (ponieważ minimalna obsługiwana wersja IOS dla dowolnej aplikacji to 4.3), nie musicie martwić się o wydanie i automatyczne wydanie.
rishabh
1
@GeneralMike: Prawdopodobnie powinno to być osobne pytanie, ale ogólnie (widzisz, co tam zrobiłem?), Chcesz upewnić się, że skopiujesz każdy obiekt z oryginału podczas głębokiej kopii - i upewnij się, że ich -copymetody również wykonują głębokie kopie .
Jeff Kelley,
6

Ta odpowiedź jest podobna do zaakceptowanej, ale używa allocWithZone:i jest aktualizowana dla ARC. NSZone jest klasą podstawową do przydzielania pamięci. Chociaż ignorowanie NSZonemoże działać w większości przypadków, nadal jest niepoprawne.

Aby poprawnie zaimplementować NSCopying, należy zaimplementować metodę protokołu, która przydziela nową kopię obiektu o właściwościach zgodnych z wartościami oryginału.

W deklaracji interfejsu w nagłówku określ, że Twoja klasa implementuje NSCopyingprotokół:

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

W implementacji .m dodaj -(id)copyWithZonemetodę, która wygląda mniej więcej tak:

- (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
źródło
2

Szybka wersja

Po prostu zadzwoń, object.copy()aby utworzyć kopię.

Nie używałem copy()dla typów wartości, ponieważ są one kopiowane „automatycznie”. Ale musiałem użyć copy()dla classtypów.

Zignorowałem NSZoneparametr, ponieważ dokumentacja twierdzi, że jest przestarzały:

Ten parametr jest ignorowany. Strefy pamięci nie są już używane przez Objective-C.

Należy również pamiętać, że jest to uproszczona implementacja. Jeśli masz podklasy robi się trochę tricker i należy użyć dynamicznego typu: 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
źródło