Best practice when implementing copyWithZone:
Asked Answered
B

4

79

I am trying to clear up a few things in my head about implementing copyWithZone:, can anyone comment on the following ...

// 001: Crime is a subclass of NSObject.
- (id)copyWithZone:(NSZone *)zone {
    Crime *newCrime = [[[self class] allocWithZone:zone] init];
    if(newCrime) {
        [newCrime setMonth:[self month]];
        [newCrime setCategory:[self category]];
        [newCrime setCoordinate:[self coordinate]];
        [newCrime setLocationName:[self locationName]];
        [newCrime setTitle:[self title]];
        [newCrime setSubtitle:[self subtitle]];
    }
    return newCrime;
}

// 002: Crime is not a subclass of NSObject.
- (id)copyWithZone:(NSZone *)zone {
    Crime *newCrime = [super copyWithZone:zone];
    [newCrime setMonth:[self month]];
    [newCrime setCategory:[self category]];
    [newCrime setCoordinate:[self coordinate]];
    [newCrime setLocationName:[self locationName]];
    [newCrime setTitle:[self title]];
    [newCrime setSubtitle:[self subtitle]];
    return newCrime;
}

In 001:

  1. Is it best to write the class name directly [[Crime allocWithZone:zone] init] or should I use [[[self Class] allocWithZone:zone] init]?

  2. Is it ok to use [self month] for copying the iVars or should I be accessing the iVars directly i.e. _month?

Brundisium answered 28/3, 2012 at 12:7 Comment(0)
S
103
  1. You should always use [[self class] allocWithZone:zone] to make sure you are creating a copy using the appropriate class. The example you give for 002 shows exactly why: Subclasses will call [super copyWithZone:zone] and expect to get back an instance of the appropriate class, not an instance of the super class.

  2. I access the ivars directly, so I don't need to worry about any side effects I might add to the property setter (e.g., generating notifications) later on. Keep in mind, subclasses are free to override any method. In your example, you are sending two extra messages per ivar. I would implement it as follows:

Code:

- (id)copyWithZone:(NSZone *)zone {
    Crime *newCrime = [super copyWithZone:zone];
    newCrime->_month = [_month copyWithZone:zone];
    newCrime->_category = [_category copyWithZone:zone];
    // etc...
    return newCrime;
}

Of course, whether you copy the ivars, retain them, or just assign them should mirror what the setters do.

Sanctimony answered 28/3, 2012 at 13:2 Comment(5)
Which of the two approaches to choose depends on whether the superclass implements NSCopying. For example, NSObject doesn't, so calling [super copyWithZone: zone] will throw an exception.Electrophorus
It says /Users/ws403216/Desktop/Demo/Demo/Crime.m:21:28: No visible @interface for 'NSObject' declares the selector 'copyWithZone:' Super class of Crime.m in my case is NSObject.Akbar
@NitinMalguri As the previous comment points out, you should only call [super copyWithZone:zone] if the parent class supports NSCopying, otherwise you should call [[[self class] allocWithZone:zone] init] and copy fields as required.Sanctimony
The default copy behavior should be a shallow copy, but you provided a solution for a deep copy. Difference between shallow and deep copy are : A shallow copy of an object will only copy the references to the objects of the original array and place them into the new array. A deep copy will actually copy the individual objects contained in the object. This done by sending each individual object the "copyWithZone:" message.Obloquy
that can clarify misunderstanding NSCoping protMaxentia
O
6

The default copy behavior of copyWithZone: method with SDK provided objects is "shallow copy". That means if you call copyWithZone: on NSString object, it will create a shallow copy but not deep copy. Difference between shallow and deep copy are :

A shallow copy of an object will only copy the references to the objects of the original array and place them into the new array.

A deep copy will actually copy the individual objects contained in the object. This done by sending each individual object the copyWithZone: message in your custom class method.

INSHORT : To get shallow copy you call retain or strong on all instance variables. To get deep copy you call copyWithZone: on all instance variables in your custom class copyWithZone: implementation. Now it's your choice to choose.

Obloquy answered 15/4, 2015 at 15:6 Comment(0)
M
1

How about this one that implement deep copy:

/// Class Foo has two properties: month and category
- (id)copyWithZone:(NSZone *zone) {
    Foo *newFoo;
    if ([self.superclass instancesRespondToSelector:@selector(copyWithZone:)]) {
        newFoo = [super copyWithZone:zone];
    } else {
        newFoo = [[self.class allocWithZone:zone] init];
    }
    newFoo->_month = [_month copyWithZone:zone];
    newFoo->_category = [_category copyWithZone:zone];
    return newFoo;
}
Motherwell answered 8/8, 2017 at 3:18 Comment(0)
D
-1

This is my model.

#import <Foundation/Foundation.h>
@interface RSRFDAModel : NSObject


@property (nonatomic, assign) NSInteger objectId;

@property (nonatomic, copy) NSString *name;

@property (nonatomic, strong) NSArray<RSRFDAModel *> *beans;


@end


#import "RSRFDAModel.h"

@interface RSRFDAModel () <NSCopying>

@end

@implementation RSRFDAModel 


-(id)copyWithZone:(NSZone *)zone {
    RSRFDAModel *model = [[[self class] allocWithZone:zone] init];

    model.objectId = self.objectId;
    model.name = self.name;
    model.beans = [self.beans mutableCopy];

    return model;
}

@end
Dwinnell answered 12/8, 2016 at 7:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.