Effective Objective-C 2.0: Item 22: Understand the NSCopying Protocol
2013-12-07 23:02
429 查看
Item 22: Understand the NSCopying Protocol
A common thing to want to do with an object is to copy it. In Objective-C, this is done through the use of the copymethod.
The way in which you can support copying of your own classes is to implement the
NSCopyingprotocol,
which contains a single method:
- (id)copyWithZone:(NSZone*)zone
This harks back to the day when
NSZonewas used to segment memory into zones, and objects were created within
a certain zone. Nowadays, every app has a single zone: the default zone. So even though this is the method you need to implement, you do not need to worry about what the zone
parameter is.
The
copymethod
is implemented in
NSObjectand simply calls
copyWithZone:with
the default zone. It’s important to remember that even though it might be tempting to override
copy, it’s
copyWithZone:that
needs to be implemented instead.
To support copying, then, all you need to do is state that you implement the
NSCopyingprotocol and
implement the single method within it. For example, consider a class representing a person. In the interface definition, you would declare that you implement
NSCopying:
Click here to view code image
#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject <NSCopying>
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
- (id)initWithFirstName:(NSString*)firstName
andLastName:(NSString*)lastName;
@end
Then, you would implement the single required method of the protocol:
Click here to view code image
- (id)copyWithZone:(NSZone*)zone {
EOCPerson *copy = [[[self class] allocWithZone:zone]
initWithFirstName:_firstName
andLastName:_lastName];
return copy;
}
This example simply passes all work in initializing the copy to the designated initializer. Sometimes, you might need to perform further work on the copy, such as if other data structures
within the class are not set up in the initializer: for example, if
EOCPersoncontained an array that was manipulated using a couple of methods to befriend and unfriend
another
EOCPerson. In that scenario, you’d also want to copy the array of friends. Following is
a full example of how this would work:
Click here to view code image
#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject <NSCopying>
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
- (id)initWithFirstName:(NSString*)firstName
andLastName:(NSString*)lastName;
- (void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
@end
@implementation EOCPerson {
NSMutableSet *_friends;
}
- (id)initWithFirstName:(NSString*)firstName
andLastName:(NSString*)lastName {
if ((self = [super init]))
{
_firstName = [firstName copy];
_lastName = [lastName copy];
_friends = [NSMutableSet new];
}
return self;
}
- (void)addFriend:(EOCPerson*)person {
[_friends addObject:person];
}
- (void)removeFriend:(EOCPerson*)person {
[_friends removeObject:person];
}
- (id)copyWithZone:(NSZone*)zone {
EOCPerson *copy = [[[self class] allocWithZone:zone]
initWithFirstName:_firstName
andLastName:_lastName];
copy->_friends = [_friends mutableCopy];
return copy;
}
@end
This time, the copying method has been changed to additionally set the
_friendsinstance variable of the copy
to a copy of its own
_friendsinstance variable. Note that the -> syntax has been used here,
since the
_friendsinstance
variable is internal. A property could have been declared for it, but since it’s never used externally, there is no need to do so.
An interesting question is raised by this example: Why is a copy taken of the
_friendsinstance
variable? You could have just as easily not taken a copy, and each object would then share the same mutable set. But that would mean that if a friend were added to the original object, it would also magically become a friend of the copy. That’s clearly not
what you would want in this scenario. But if the set were immutable, you might have chosen to not take a copy, since the set cannot change, anyway. This would save having two identical sets lying around in memory.
You should generally use the designated initializer to initialize the copy, just as in this example. But you wouldn’t want to do this when the designated initializer has a side effect
that you don’t want to happen to the copy, such as setting up complex internal data structures that you are immediately going to overwrite.
If you look back up to the
copyWithZone:method, you will see that the friends set is copied using the
mutableCopymethod.
This comes from another protocol, called
NSMutableCopying. It is similar to
NSCopyingbut
defines the following method instead:
- (id)mutableCopyWithZone:(NSZone*)zone
The
mutableCopyhelper is just like the copy helper and calls the preceding method with the default zone. You
should implement
NSMutableCopyingif
you have mutable and immutable variants of your class. When using this pattern, you should not override
copyWithZone:in your mutable class to return a mutable
copy. Instead, if a mutable copy is required of either an immutable or a mutable instance, you should use
mutableCopy.Similarly, if an immutable copy is required, you should use
copy.
The following holds true in the case of
NSArrayand
NSMutableArray:
Click here to view code image
-[NSMutableArray copy] => NSArray
-[NSArray mutableCopy] => NSMutableArray
Note the subtlety with calling
copyon a mutable object and being given an instance of another class, the immutable variant. This is done so
that it is easy to switch between mutable and immutable variants. Another way this could have been achieved is to have three methods:
copy,
immutableCopy,and
mutableCopy,where
copyalways
returns the same class, but the other two return specific variants. However, this would not be good if you don’t know whether the instance you have is immutable. You may decide to call
copyon
something you have been returned as an
NSArraybut in fact it’s an
NSMutableArray.
In that case, you would assume that you have an immutable array returned, but instead it’s a mutable one.
You could introspect (see Item
14) to determine what type of instance you have, but that would add complexity everywhere a copy is taken. So you would end up always using
immutableCopyor
mutableCopyto
be on the safe side, in which case, it’s back to just having just two methods, which is exactly the same as having only
copyand
mutableCopy. The
benefit of calling it
copyinstead of
immutableCopyis
that
NSCopyingis
designed not only for classes with mutable and immutable variants but also cases in which there is not the distinction, so
immutableCopywould be a badly named method.
Another decision to make with your copying method is whether to perform a deep or a shallow copy. A deep
copy copies all the backing data as well. Copying by default for all the collection classes in Foundation is shallow, meaning that only the container is copied, not the data stored
within the container. This is mainly because objects within the container might not be able to be copied; also, it’s usually not desirable to copy every object. The difference between a deep and a shallow copy is illustrated inFigure
3.2.
Figure 3.2 Shallow versus deep copy. The contents of a shallow copy point to the same objects as the original. The contents of a deep copy point to copies of the original
contents.
Usually, you will want your own classes to follow the same pattern as used in the system frameworks, with
copyWithZone:performing
a shallow copy. But if required, a method can be added to perform a deep copy. In the case of
NSSet,this is provided through the following initializer method:
- (id)initWithSet:(NSArray*)array
copyItems:(BOOL)copyItems
If
copyItemsis set to
YES, the items in the array are sent the
copymessage
to create a copy to build up the new set to return.
In the example of the
EOCPersonclass, the set containing the friends is copied in
copyWithZone:,but
as discussed, this does not copy the items in the set themselves. But if such a deep copy were required, you could provide a method such as the following:
Click here to view code image
- (id)deepCopy {
EOCPerson *copy = [[[self class] alloc]
initWithFirstName:_firstName
andLastName:_lastName];
copy->_friends = [[NSMutableSet alloc] initWithSet:_friends
copyItems:YES];
return copy;
}
No protocol defines deep copying, so it is left up to each class to define how such a copy is made. You
simply need to decide whether you need to provide a deep copy method. Also, you should never assume that an object conforming to
NSCopyingwill
be performing a deep copy. In the vast majority of cases, it will be a shallow copy. If you need a deep copy of any object, either find the relevant method, or assume that you have to create your own, unless the documentation states that its implementation
of
NSCopyingis providing a deep copy.
Things to Remember
Implement the
NSCopyingprotocol
if your object will need to be copied.
If your object
has mutable and immutable variants, implement both the
NSCopyingand
NSMutableCopyingprotocols.
Decide whether
a copy will be shallow or deep, and prefer shallow, where possible, for a normal copy.
Consider adding
a deep-copy method if deep copies of your object will be useful.
相关文章推荐
- Effective Objective-C 2.0: Item 29: Understand Reference Counting
- Effective Objective-C 2.0: Item 37: Understand Blocks
- Effective Objective-C 2.0: Item 12: Understand Message Forwarding
- Effective Objective-C 2.0: Item 21: Understand the Objective-C Error Model
- Item 22: 当使用Pimpl机制时,在实现文件中给出特殊成员函数的实现
- Understand 2.0使用
- Effective Java 2.0_中英文对照_Item 1
- 重叠构造函数模式_Effective Java 2.0_Item 2知识点
- Effective Java 2.0_中英文对照_Item 3
- Effective Java 2.0_中文版_Item 7
- Item22 When using the Pimpl, define specific member functions in the implementation file
- 读书笔记 effective c++ Item 22 将数据成员声明成private
- Item 8:Understand the different meanings of new and delete.(More Effective C++)
- <Effective Mordern C++>笔记:Item 3:Understand decltype .
- Effective Java 2.0_中英文对照_Item 7
- ehci-hcd 00:1d.7: USB 2.0 enabled, EHCI 1.00, driver 2003-Jan-22
- 代码阅读分析工具Understand 2.0试用
- Item28 Understand reference collapsing
- Effective Objective-C 2.0:Item 25: Always Prefix Category Names on Third-Party Classes
- Effective Objective-C 2.0: Item 32: Beware of Memory Management with Exception-Safe Code