Implementing NSCopying

IphoneIosObjective CNscopying

Iphone Problem Overview


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 Cars. 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.

Iphone Solutions


Solution 1 - Iphone

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

Solution 2 - Iphone

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

Solution 3 - Iphone

#Swift Version

Just call object.copy() to create the copy.

I didn't use copy() for value types since those are copied "automatically." But I had to use copy() for class types.

I ignored the NSZone parameter because docs say it is deprecated:

> This parameter is ignored. Memory zones are no longer used by > Objective-C.

Also, please note that this is a simplified implementation. If you have subclasses it gets a bit tricker and you should use dynamic type: 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
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionuser440096View Question on Stackoverflow
Solution 1 - IphoneJeff KelleyView Answer on Stackoverflow
Solution 2 - IphoneJustin MeinersView Answer on Stackoverflow
Solution 3 - IphonekgaidisView Answer on Stackoverflow