如何让自定义对象支持 copy 操作?是重写 copy 方法么?当然不是,而是需要让自定义类实现 NSCopying 协议,该协议只有一个方法:
- (id)copyWithZone:(nullable NSZone *)zone;
以前开发程序时,会把内存分成不同的"区",而对象会创建在某个区里。现在不用了,每个程序只有一个"默认区"(default zone),实现该方法时,不必担心其中的 zone 参数。例如,我们要让自定义的 ACLStudent
类支持拷贝功能,则可以像如下这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| ACLStudent.h
@interface ACLStudent : NSObject <NSCopying>
@property (nonatomic, assign, readonly) NSInteger studentId; @property (nonatomic, copy, readonly) NSString *firstName; @property (nonatomic, copy, readonly) NSString *lastName;
- (instancetype)initWithStudentId:(NSInteger)studentId firstName:(NSString *)firstName lastName:(NSString *)lastName; @end -----
ACLStudent.m
@implementation ACLStudent
- (instancetype)initWithStudentId:(NSInteger)studentId firstName:(NSString *)firstName lastName:(NSString *)lastName { self = [super init]; if (self) { _studentId = studentId; _firstName = [firstName copy]; _lastName = [lastName copy]; } return self; }
- (instancetype)copyWithZone:(NSZone *)zone { ACLStudent *copy = [[[self class] allocWithZone:zone] initWithStudentId:_studentId firstName:_firstName lastName:_lastName]; return copy; }
+ (BOOL)accessInstanceVariablesDirectly { return YES; }
@end
|
上例中 copyWithZone:
方法使用了全能初始化方法(designated initializer)来执行拷贝对象的所有初始化工作。这里需要注意,对于未能在全能初始化方法中设置好的变量,需要在 copyWithZone:
方法中做特殊处理。例如,如果在 ACLStudent
类中增加一个变量,来存储当前学生所选选修课的科目,完整代码变成如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| ACLStudent.h
@class ACLCourse; @interface ACLStudent : NSObject <NSCopying>
@property (nonatomic, assign, readonly) NSInteger studentId; @property (nonatomic, copy, readonly) NSString *firstName; @property (nonatomic, copy, readonly) NSString *lastName;
- (instancetype)initWithStudentId:(NSInteger)studentId firstName:(NSString *)firstName lastName:(NSString *)lastName;
- (void)addElective:(ACLCourse *)course;
- (void)removeElective:(ACLCourse *)course; @end --------
@implementation ACLStudent { NSMutableSet *_electives; /**< 选修科目 */ }
- (instancetype)initWithStudentId:(NSInteger)studentId firstName:(NSString *)firstName lastName:(NSString *)lastName { self = [super init]; if (self) { _studentId = studentId; _firstName = [firstName copy]; _lastName = [lastName copy]; _electives = [NSMutableSet new]; } return self; }
- (void)addElective:(ACLCourse *)course { [_electives addObject:course]; }
- (void)removeElective:(ACLCourse *)course { [_electives removeObject:course]; }
- (instancetype)copyWithZone:(NSZone *)zone { ACLStudent *copy = [[[self class] allocWithZone:zone] initWithStudentId:_studentId firstName:_firstName lastName:_lastName]; copy->_electives = [[NSMutableSet alloc] initWithSet:_electives copyItems:YES]; return copy; }
+ (BOOL)accessInstanceVariablesDirectly { return NO; }
@end
|
注意全能初始化方法和 copyWithZone:
方法实现的变化, 这里我们对 _electives
对象执行的是深拷贝。
假如我们想让自定义对象支持 mutableCopy
操作,那又应该怎么操作呢?这需要自定义对象遵循 NSMutableCopying
协议, 该协议也只有一个方法:
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
其与 copy 相似,也是使用默认的 zone 参数来调用 mutableCopyWithZone:
。
如果类分为可变版本和不可变版本,那么就应该实现 NSMutableCopying
, 且在可变类中覆写 copyWithZone:
方法时,应该返回一份不可变的版本。无论当前实例是否可变,若需获取其可变版本的拷贝,均应调用 mutableCopy
方法,若需不可变的拷贝,则总应该调用 copy
方法。例如对于不可变的 NSArray 和可变的 NSMutableArray,以下关系总是成立:
1 2
| - [NSMutableArray copy] => NSArray - [NSArray mutableCopy] => NSMutableArray
|
如何对 NSArray
执行深拷贝呢?苹果官方文档 Copying Collections 提供了以下两种方法。
方法1:
1
| NSArray *deepCopyArray=[[NSArray alloc] initWithArray:someArray copyItems:YES];
|
这种方式执行拷贝时,someArray
中的可变对象是执行深拷贝,而对于不可变对象,仍然执行的是浅拷贝。
方法2:
1
| NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
|
这种方式实现的是真正意义上的深拷贝,oldArray
中所有元素都是深拷贝。
那么 copy
和 mutableCopy
返回的对象是执行的深拷贝还是浅拷贝呢?这篇博客 [copy与mutableCopy][] 对系统对象执行 copy
和 mutableCopy
时到底执行的深拷贝还是浅拷贝进行了讨论。其中系统对象分为了两类:
- 系统的非容器类对象,如
NSString
、NSMutableString
、NSNumber
等。
- 系统的容器类对象,如
NSArray
、NSMutableArray
、NSDictionary
、NSMutableDictionary
等。
现把结论摘抄如下:
- 对于系统的非容器类对象,如果对一不可变对象(如
NSString
)复制,copy
是指针复制(浅拷贝)和 mutableCopy
就是对象复制(深拷贝); 如果是对可变对象(如 NSMutableString
)复制,copy
和 mutableCopy
都是深拷贝,但是 copy
返回的对象是不可变的。
- 对于系统的容器类对象,对不可变对象(如
NSArray
)进行复制,copy
是指针复制(浅拷贝), mutableCopy
是对象复制(深拷贝), 但是不管是 copy
还是 mutableCopy
, 且不论容器内对象是可变还是不可变,返回的容器内对象都是指针复制(浅拷贝)。
- 对于系统的容器类对象,对可变对象(如
NSMutableArray
)进行复制时,copy
和 mutableCopy
都是对象复制(深拷贝),但是不管是 copy
还是 mutableCopy
,且不论容器内对象是可变还是不可变,返回的容器内对象都是指针复制(浅拷贝)。
如果觉得本文对你有帮助,就请用微信随意打赏我吧^_^