Objective-C 复制对象

本文并不是一篇完整的教程,更像一篇快速笔记,讲解 Objective-C 中的复制对象。
复制对象
基础实现
复制对象需要实现 NSCopying 协议:
#import <Foundation/Foundation.h>
@interface HVUser : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *mobile;
@property (nonatomic) int gender;
@end
#import "HVUser.h"
@implementation HVUser
- (id)copyWithZone:(NSZone *)zone {
HVUser *user = [[[self class] allocWithZone:zone] init];
user.name = self.name;
user.mobile = self.mobile;
user.gender = self.gender;
return user;
}
@end
HVUser *user1 = [HVUser new];
user1.name = @"Jack";
HVUser *user2 = [user1 copy];
user2.name = @"Mick";
如果需要通过拷贝区分得到可变实例和不可变实例,还需要再实现 NSMutableCopying 协议:
- (id)mutableCopyWithZone:(NSZone *)zone
另外,还需要注意拷贝的实现是深拷贝,还是浅拷贝。
常见问题
- copy 始终返回不可变实例
- mutableCopy 始终返回可变实例
-[NSMutableArray copy] => NSArray
-[NSArray mutableCopy] => NSMutableArray
先看下面的代码:
@interface MyPerson : NSObject
@property (nonatomic, copy) NSMutableArray *friends;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyPerson *person = [MyPerson new];
NSMutableArray *oldFriends = [NSMutableArray arrayWithObjects:@"Mick", @"Lucy", @"Tom", nil];
person.friends = oldFriends;
[person.friends addObject:@"Jack"]; // crash here
}
return 0;
}
运行后,会 crash:
2020-09-01 09:57:33.425804+0800 OCDemo[1147:20848] -[__NSArrayI addObject:]: unrecognized selector sent to instance 0x10059b890
2020-09-01 09:57:33.430715+0800 OCDemo[1147:20848] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x10059b890'
其原因是 oldFriends 赋值给 person.friends 时,因为属性声明为 copy,对 oldFriends 先执行 copy 方法,再赋值,前面说了 copy 始终返回不可变实例,所以这里的 person.friends 底层是 NSArray,再其上执行 addObject 就会 Crash:
person.friends = [oldFriends copy];
再修改一下代码,这样的语义对使用者来说没有什么问题,但这可能并不满足需求:
@interface MyPerson : NSObject
@property (nonatomic, strong) NSMutableArray *friends;
@end
再执行下面的代码会输出 4,是因为 person.friends 和 oldFriends 指向同一个 NSMutableArray:
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyPerson *person = [MyPerson new];
NSMutableArray *oldFriends = [NSMutableArray arrayWithObjects:@"Mick", @"Lucy", @"Tom", nil];
person.friends = oldFriends;
[person.friends addObject:@"Jack"];
NSLog(@"%ld", oldFriends.count);
}
return 0;
}
再修改一下代码,这样的语义对使用者来说没有什么问题,也就不能改变 friends,但这可能并不满足需求,可能还需要修改 friends:
@interface MyPerson : NSObject
@property (nonatomic, copy) NSArray *friends;
@end
最后再修改一下代码,setFriends 方法中 friends 的底层实现是 NSArray,因为被 copy 了一次,在其上再调用 mutableCopy 返回可变实例 NSMutableArray,再将其赋值给 _friends,这样也就满足了语义,也不会带来什么问题:
@interface MyPerson : NSObject
@property (nonatomic, copy) NSMutableArray *friends;
@end
@implementation MyPerson
- (void)setFriends:(NSMutableArray *)friends {
_friends = [friends mutableCopy];
}
@end