Implementing NSCopying
I've read the NSCopying
docs but I am still very unsure about how to implement what is required.
My class 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
The Vendor
class has an array of objects called Car
.
My Car
object:
@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, Vendor
holds an array of Car
objects. Car
holds 2 arrays of other custom objects.
Both Vendor
and Car
are init from a dictionary. I'll add one of these methods, they may or may not be relevant.
-(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;
}
So to summarize the scary problem.
I need to copy an array of Vendor
objects. I believe I need to implement the NSCopying
protocol on Vendor
, which may mean I need to implement it also on Car
since Vendor
holds an array of Car
s. That means I also need to implement it on the classes that are held in the 2 arrays belonging to the Car
object.
I'd really appreciate it if I could get some guidance on implementing NSCopying
protocol on Vendor
, I can't find any tutorials on this anywhere.
To implement NSCopying, your object must respond to the -copyWithZone:
selector. Here’s how you declare that you conform to it:
@interface MyObject : NSObject <NSCopying> {
Then, in your object’s implementation (your .m
file):
- (id)copyWithZone:(NSZone *)zone
{
// Copying code here.
}
What should your code do? First, create a new instance of the object—you can call [[[self class] alloc] init]
to get an initialized obejct of the current class, which works well for subclassing. Then, for any instance variables that are a subclass of NSObject
that supports copying, you can call [thatObject copyWithZone:zone]
for the new object. For primitive types (int
, char
, BOOL
and friends) just set the variables to be equal. So, for your obejct Vendor, it’d look like this:
- (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;
}
This answer is similar to the accepted, but uses allocWithZone:
and is updated for ARC. NSZone is foundation class for allocating memory. While ignoring NSZone
might work for most cases, it is still incorrect.
To correctly implement NSCopying
you must implement a protocol method which allocates a new copy of the object, with properties that match the values of the original.
In the interface declaration in the header, specify that your class implements the NSCopying
protocol:
@interface Car : NSObject<NSCopying>
{
...
}
In the .m implementation add a -(id)copyWithZone
method which looks something like the following:
- (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;
}