Przeczytałem NSCopying
dokumenty, 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
Vendor
Klasa ma tablicę obiektów zwanych Car
.
Mój Car
obiekt:
@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 Vendor
przechowuje tablicę Car
obiektów. Car
zawiera 2 tablice innych obiektów niestandardowych.
Obie Vendor
i Car
są 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ę Vendor
obiektów. Uważam, że muszę zaimplementować NSCopying
protokół Vendor
, co może oznaczać, że muszę go również zaimplementować, Car
ponieważ Vendor
zawiera tablicę Car
s. Oznacza to, że muszę go również zaimplementować na klasach, które są przechowywane w 2 tablicach należących do Car
obiektu.
Naprawdę byłbym wdzięczny, gdybym mógł uzyskać wskazówki dotyczące implementacji NSCopying
protokołu Vendor
, nigdzie nie mogę znaleźć żadnych tutoriali na ten temat.
źródło
Odpowiedzi:
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
.m
plik):- (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ąNSObject
obsługującą kopiowanie, można wywołać[thatObject copyWithZone:zone]
nowy obiekt. Dla typów pierwotnych (int
,char
,BOOL
i 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; }
źródło
copy
jest 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.copyWithZone:
zwraca obiekt z liczbą odwołań równą 1 i bez autorelease spowoduje to wyciek. Musisz dodać przynajmniej autorelease.[[self class] alloc]
używaćallocWithZone
zamiast tego? Przepraszam, że o tym wspominam.-copy
metody również wykonują głębokie kopie .Ta odpowiedź jest podobna do zaakceptowanej, ale używa
allocWithZone:
i jest aktualizowana dla ARC. NSZone jest klasą podstawową do przydzielania pamięci. Chociaż ignorowanieNSZone
moż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
NSCopying
protokół:@interface Car : NSObject<NSCopying> { ... }
W implementacji .m dodaj
-(id)copyWithZone
metodę, 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; }
źródło
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()
dlaclass
typów.Zignorowałem
NSZone
parametr, ponieważ dokumentacja twierdzi, że jest przestarzały: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 } }
źródło