Implementing NSCopying
Asked Answered
A

3

86

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.

Allan answered 3/11, 2010 at 16:28 Comment(2)
Have you read the documentation of NSCopying? I found it quite clear when needed.Hauger
Yes, read and it re-read it. I rarely find apple docs easy to learn from, though they are great for finding methods etc while programming. Thanks -CodeAllan
C
187

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;
}
Carolynecarolynn answered 3/11, 2010 at 16:42 Comment(14)
Hi Jeff, Is it really as simple as that? Also do I need to apply the same things to the Car class and all custom objects that it contains arrays of? Basically step down the whole way. Or would it only need to be applied to Vendor? Many Thanks,Allan
@Code: copy is typically implemented as a shallow copy like Jeff showed. It's unusual — though not inconceivable — that you'd want a full-on deep copy (where everything all the way down is copied). Deep copies are a lot more trouble, too, so you generally want to be sure that's really what you want.Lambrequin
@Code: You’ll want to implement this for anything you plan on copying with -copy or -copyWithZone:. Note that as @Lambrequin says, this will be a “shallow copy,” so the arrays may point to the same objects in the original and the copy. If those objects change, you may need to copy them as well.Carolynecarolynn
There is a problem in your code where you copy your subclasses, as copyWithZone: returns a object with reference count of 1 and no autorelease this will cause a leak. You need to add at least an autorelease.Kherson
Do you mean instance variables instead of subclasses? You’re right about those, I’ll edit the answer.Carolynecarolynn
Shouldn't [[self class] alloc] use allocWithZone instead? Sorry for bringing this up.Kaveri
@Kaveri You’re probably correct, but in typical use this won’t matter much.Carolynecarolynn
Folks, I suppose by using ARC(since the minimum supported IOS for any app is 4.3), you need not worry about release & auto-release.Angelia
Let's say I do need a deep copy. How would the code for that be different from the code here?Stopple
@GeneralMike: This should probably be a separate question, but in general (see what I did there?), you want to make sure to copy every object from the original during a deep copy—and make sure that their -copy methods also do deep copies.Carolynecarolynn
@JeffKelley: Yeah that's what I figured, which is why I didn't post another Q. I was just wondering if there was a better way than a loop-and-copy type thing, but I guess not. Thx for the help.Stopple
And if the class is immutable, just return object itself without creating a new object.Asha
@Kaveri you are correct. I added a new answer with this correction.Giddens
I need more explaining here. In both NSObjcet.h and the documentation, copyWithZone: is always a Class method! +(id) copyWithZone: etc. That really confused me, because how can a class method copy a specific instance it doesn't know about? Furthermore the copyWithZone: method definition in NSObject.h - is not available when using ARC!!!! so I can't even implement it. Please advise/explainAppendectomy
G
7

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;
}
Giddens answered 3/1, 2017 at 17:19 Comment(0)
H
3

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
    }
}
Horror answered 8/1, 2018 at 16:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.