最近听到朋友谈论在面试中被面试官通过基础知识深挖狂虐的事情,心中有些不忿,决定推出一系列基础知识重温的文章,在方便自己复习的同时,希望和大家一块进步。
这一篇文章主要对mutableCopy,copy进行复习,解惑。iOS中并不是所有的对象都支持copy,mutableCopy,遵守NSCopying 协议的类可以发送copy消息,遵守NSMutableCopying 协议的类才可以发送mutableCopy消息。假如发送了一个没有遵守上诉两协议而发送 copy或者 mutableCopy,那么就会发生异常。但是默认的ios类并没有遵守这两个协议。如果想自定义一下copy 那么就必须遵守NSCopying,并且实现 copyWithZone: 方法,如果想自定义一下mutableCopy 那么就必须遵守NSMutableCopying,并且实现 mutableCopyWithZone: 方法。我们用如下代码进行验证:
@interface Person : NSObject
@property (nonatomic,strong)NSString *name;
@property (nonatomic,assign)NSInteger age;
@end
Person *jack = [Person new];
jack.name = @"jack";
jack.age = 1;
NSLog(@"jack:%p",jack);
NSLog(@"jack.name:%p",jack.name);
Person *lucy = [jack copy];
NSLog(@"lucy:%p",lucy);
NSLog(@"lucy.name:%p",lucy.name);
在运行的时候发生崩溃,崩溃信息如下:
2017-04-30 16:51:56.373 interviewDemo[13770:2541374] -[Person copyWithZone:]: unrecognized selector sent to instance 0x60000003e800
2017-04-30 16:51:56.434 interviewDemo[13770:2541374] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person copyWithZone:]: unrecognized selector sent to instance 0x60000003e800'
我们发现Person类没有遵守NSCopying协议,如果对其实例对象执行copy操作就会出现崩溃。由于没有遵守NSMutableCopying协议同样对其实例对象执行mutableCopy操作同样会崩溃。
iOS中遵守NSCopying
协议的类有:NSString,NSValue,NSArray,NSDictionary,NSSet
;遵守NSMutableCopying
协议的类有: NSString,NSValue,NSArray,NSDictionary,NSSet
当然了别忘了这些类的子类哦。
接下来为大家说一下copy和retain的区别。copy是创建一个新对象,retain是创建一个指针,引用对象计数加1。Copy属性表示两个对象内容相同,新的对象retain为1 ,与旧有对象的引用计数无关,旧有对象没有变化。copy减少对象对上下文的依赖。 retain属性表示两个对象地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的retain值+1也就是说,retain 是指针拷贝,copy 是内容拷贝。
注意:声明属性时用strong或者retain效果是一样的,我这里在ARC环境下使用strong和copy进行验证比较,示例代码如下:
strong代码
@interface Person : NSObject
@property (nonatomic,strong)NSString *name;
@property (nonatomic,assign)NSInteger age;
@property (nonatomic,strong)Person *child;
@end
Person *xiaoJack = [Person new];
xiaoJack.name = @"小jack";
Person *jack = [Person new];
jack.name = @"jack";
jack.child =xiaoJack;
NSLog(@"xiaoJack %p",xiaoJack);
NSLog(@"jack.child %p",jack.child);
NSLog(@"xiaoJack retainCount %@",[xiaoJack valueForKey:@"retainCount"]);
运行结果如下:
2017-04-30 18:12:17.995 interviewDemo[14122:2595610] xiaoJack 0x60800002fd20
2017-04-30 18:12:17.995 interviewDemo[14122:2595610] jack.child 0x60800002fd20
2017-04-30 18:12:17.996 interviewDemo[14122:2595610] xiaoJack retainCount 2
我们发现xiaoJack 和jack.child指向的相同的内存地址,而且xiaoJack的retainCount为2,说明 strong/retain操作是指针拷贝。
copy代码:
@interface Person : NSObject<NSCopying>
@property (nonatomic,copy)NSString *name;
@property (nonatomic,assign)NSInteger age;
@property (nonatomic,copy)Person *child;
-(id)copyWithZone:(NSZone *)zone;
@end
Person *xiaoJack = [Person new];
xiaoJack.name = @"小jack";
Person *jack = [Person new];
jack.name = @"jack";
jack.child =xiaoJack;
NSLog(@"xiaoJack %p",xiaoJack);
NSLog(@"jack.child %p",jack.child);
NSLog(@"xiaoJack retainCount %@",[xiaoJack valueForKey:@"retainCount"]);
运行结果如下:
2017-04-30 18:15:26.720 interviewDemo[14164:2598814] xiaoJack 0x608000037be0
2017-04-30 18:15:26.720 interviewDemo[14164:2598814] jack.child 0x60800003e280
2017-04-30 18:15:26.721 interviewDemo[14164:2598814] xiaoJack retainCount 1
我们可以发现xiaoJack指向的内存地址和jack.child指向的内存地址不一样,并且xiaoJack的retainCount仍然为1 。说明copy操作是创建一个新对象。
经过验证,用户自定义类实现NSCopying协议的会对对象进行复制创建一个新对象,而系统提供的实现了NSCopying协议的,确实指针copy。
我的代码如下:
-(id)copyWithZone:(NSZone *)zone{
ClassA *copy = [[[self class] allocWithZone:zone] init];
return copy;
}
欢迎大家一块讨论
在验证copy与retain/strong 区别时候发现NSString,NSNumber属于常量类型,retainCount显示不受retain,release影响,是一个我们预期意外的值,不能做为参考使用。
下面是我从网上找来的一张图片
根据苹果官方的定义,iOS系统定义的对象分为系统的非容器类对象和系统的容器类对象。系统的非容器类对象有NSString,NSNumber(不支持NSCopying,NSMutableCopying)。对于NSString的,copy,mutableCopy执行的操作如上表所示,我这里已经验证过了。但是对于系统的容器类对象NSArray,NSDictionary,NSSet,其元素对象始终是指针复制。我这里很有必要为大家进行验证一下,
1,Person不支持NSCopying
@interface Person : NSObject
@property (nonatomic,copy)NSString *name;
@property (nonatomic,assign)NSInteger age;
@property (nonatomic,copy)Person *child;
@end
Person *jack = [Person new];
jack.name = @"jack";
jack.age = 1;
Person *lucy = [Person new];
lucy.name = @"lucy";
lucy.age = 2;
NSArray *array1 = @[jack,lucy];
NSArray *array2 = [array1 copy];
NSLog(@"array1 retainCount %@",[array1 valueForKey:@"retainCount"]);
NSLog(@"array1 %p",array1);
for (int i =0; i.count; i++) {
Person *person = array1[i];
NSLog(@"person:%p",person);
}
NSLog(@"***********");
NSLog(@"array2 %p",array2);
for (int i =0; i.count; i++) {
Person *person = array2[i];
NSLog(@"person:%p",person);
}
NSLog(@"***********");
运行结果如下:
2017-05-01 08:42:32.993 interviewDemo[14956:2719378] array1 retainCount (
2,
2
)
2017-05-01 08:42:32.994 interviewDemo[14956:2719378] array1 0x600000226d40
2017-05-01 08:42:32.994 interviewDemo[14956:2719378] person:0x600000226d80
2017-05-01 08:42:32.994 interviewDemo[14956:2719378] person:0x600000226d60
2017-05-01 08:42:32.994 interviewDemo[14956:2719378] ***********
2017-05-01 08:42:32.994 interviewDemo[14956:2719378] array2 0x600000226d40
2017-05-01 08:42:32.995 interviewDemo[14956:2719378] person:0x600000226d80
2017-05-01 08:42:32.995 interviewDemo[14956:2719378] person:0x600000226d60
2017-05-01 08:42:32.995 interviewDemo[14956:2719378] ***********
发现array1,array2指向同样的内存地址,array1,array2内的元素也指向同样的内存地址。array1的retainCount操作显示的是内部元素的retainCount都为2,可见内部元素进行了strong/retain操作。
2,Person支持NSCopying
@interface Person : NSObject<NSCopying>
@property (nonatomic,copy)NSString *name;
@property (nonatomic,assign)NSInteger age;
@property (nonatomic,copy)Person *child;
-(id)copyWithZone:(NSZone *)zone;
@end
@implementation Person
-(id)copyWithZone:(NSZone *)zone{
Person *copy = [[[self class] allocWithZone:zone] init];
copy->_name = [_name copy];
copy->_age = _age;
return copy;
}
@end
Person *jack = [Person new];
jack.name = @"jack";
jack.age = 1;
Person *lucy = [Person new];
lucy.name = @"lucy";
lucy.age = 2;
NSArray *array1 = @[jack,lucy];
NSArray *array2 = [array1 copy];
NSLog(@"array1 retainCount %@",[array1 valueForKey:@"retainCount"]);
NSLog(@"array1 %p",array1);
for (int i =0; i.count; i++) {
Person *person = array1[i];
NSLog(@"person:%p",person);
}
NSLog(@"***********");
NSLog(@"array2 %p",array2);
for (int i =0; i.count; i++) {
Person *person = array2[i];
NSLog(@"person:%p",person);
}
NSLog(@"***********");
运行结果如下:
2017-05-01 09:29:24.575 interviewDemo[15094:2757902] array1 retainCount (
2,
2
)
2017-05-01 09:29:24.575 interviewDemo[15094:2757902] array1 0x608000033920
2017-05-01 09:29:24.575 interviewDemo[15094:2757902] person:0x608000033600
2017-05-01 09:29:24.575 interviewDemo[15094:2757902] person:0x6080000335e0
2017-05-01 09:29:24.575 interviewDemo[15094:2757902] ***********
2017-05-01 09:29:24.576 interviewDemo[15094:2757902] array2 0x608000033920
2017-05-01 09:29:24.576 interviewDemo[15094:2757902] person:0x608000033600
2017-05-01 09:29:24.576 interviewDemo[15094:2757902] person:0x6080000335e0
2017-05-01 09:29:24.576 interviewDemo[15094:2757902] ***********
发现array1,array2指向同样的内存地址,array1,array2内的元素也指向同样的内存地址。array1的retainCount操作显示的是内部元素的retainCount都为2,可见内部元素进行了strong/retain操作。
3,Person不支持NSMutableCopying
@interface Person : NSObject
@property (nonatomic,copy)NSString *name;
@property (nonatomic,assign)NSInteger age;
@property (nonatomic,copy)Person *child;
@end
Person *jack = [Person new];
jack.name = @"jack";
jack.age = 1;
Person *lucy = [Person new];
lucy.name = @"lucy";
lucy.age = 2;
NSArray *array1 = @[jack,lucy];
NSArray *array2 = [array1 mutableCopy];
NSLog(@"array1 retainCount %@",[array1 valueForKey:@"retainCount"]);
NSLog(@"array1 %p",array1);
for (int i =0; i.count; i++) {
Person *person = array1[i];
NSLog(@"person:%p",person);
}
NSLog(@"***********");
NSLog(@"array2 %p",array2);
for (int i =0; i.count; i++) {
Person *person = array2[i];
NSLog(@"person:%p",person);
}
NSLog(@"***********");
运行结果:
2017-05-01 10:07:37.822 interviewDemo[15227:2771155] array1 retainCount (
3,
3
)
2017-05-01 10:07:37.823 interviewDemo[15227:2771155] array1 0x600000222400
2017-05-01 10:07:37.823 interviewDemo[15227:2771155] person:0x600000222160
2017-05-01 10:07:37.823 interviewDemo[15227:2771155] person:0x600000222360
2017-05-01 10:07:37.823 interviewDemo[15227:2771155] ***********
2017-05-01 10:07:37.823 interviewDemo[15227:2771155] array2 0x6000002451c0
2017-05-01 10:07:37.823 interviewDemo[15227:2771155] person:0x600000222160
2017-05-01 10:07:37.823 interviewDemo[15227:2771155] person:0x600000222360
2017-05-01 10:07:37.824 interviewDemo[15227:2771155] ***********
发现array1,array2指向不同的内存地址,array1,array2内的元素却指向同样的内存地址。array1的retainCount操作显示的是内部元素的retainCount都为3,可见内部元素进行了strong/retain操作。
4,Person支持NSMutableCopying
@interface Person : NSObject<NSMutableCopying>
@property (nonatomic,copy)NSString *name;
@property (nonatomic,assign)NSInteger age;
@property (nonatomic,copy)Person *child;
-(id)mutableCopyWithZone:(NSZone *)zone;
@end
@implementation Person
-(id)mutableCopyWithZone:(NSZone *)zone{
Person *copy = NSCopyObject(self, 0, zone);//在非ARC下使用
copy->_name = [_name mutableCopy];
copy->_age = _age;
return copy;
}
@end
Person *jack = [Person new];
jack.name = @"jack";
jack.age = 1;
Person *lucy = [Person new];
lucy.name = @"lucy";
lucy.age = 2;
NSArray *array1 = @[jack,lucy];
NSArray *array2 = [array1 mutableCopy];
NSLog(@"array1 retainCount %@",[array1 valueForKey:@"retainCount"]);
NSLog(@"array1 %p",array1);
for (int i =0; i.count; i++) {
Person *person = array1[i];
NSLog(@"person:%p",person);
}
NSLog(@"***********");
NSLog(@"array2 %p",array2);
for (int i =0; i.count; i++) {
Person *person = array2[i];
NSLog(@"person:%p",person);
}
NSLog(@"***********");
运行结果如下:
2017-05-01 10:11:49.070 interviewDemo[15256:2774681] array1 retainCount (
3,
3
)
2017-05-01 10:11:49.070 interviewDemo[15256:2774681] array1 0x60800042dc20
2017-05-01 10:11:49.070 interviewDemo[15256:2774681] person:0x60800022c920
2017-05-01 10:11:49.070 interviewDemo[15256:2774681] person:0x608000222740
2017-05-01 10:11:49.071 interviewDemo[15256:2774681] ***********
2017-05-01 10:11:49.071 interviewDemo[15256:2774681] array2 0x6080000534d0
2017-05-01 10:11:49.071 interviewDemo[15256:2774681] person:0x60800022c920
2017-05-01 10:11:49.071 interviewDemo[15256:2774681] person:0x608000222740
2017-05-01 10:11:49.071 interviewDemo[15256:2774681] ***********
发现array1,array2指向不同的内存地址,array1,array2内的元素却指向同样的内存地址。array1的retainCount操作显示的是内部元素的retainCount都为3,可见内部元素进行了strong/retain操作。
如果想对容器对象的元素进行复制,容器内的元素支持NSCoding,NSMutableCopying协议。
@interface Person : NSObject<NSMutableCopying,NSCoding>
@property (nonatomic,copy)NSString *name;
@property (nonatomic,assign)NSInteger age;
-(id)mutableCopyWithZone:(NSZone *)zone;
@end
@implementation Person
-(id)mutableCopyWithZone:(NSZone *)zone{
Person *copy = NSAllocateObject([self class], 0, zone);
copy->_name = [_name mutableCopy];
copy->_age = _age;
return copy;
}
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:_name forKey:@"name"];
[aCoder encodeObject:@(_age) forKey:@"age"];
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder{
self = [super init];
if (self) {
_name = [aDecoder decodeObjectForKey:@"name"];
_age = [[aDecoder decodeObjectForKey:@"age"] integerValue];
}
return self;
}
@end
Person *jack = [Person new];
jack.name = @"jack";
jack.age = 1;
Person *lucy = [Person new];
lucy.name = @"lucy";
lucy.age = 2;
NSArray *array1 = @[jack,lucy];
NSArray* array2 = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject: array1]];
NSLog(@"array1 retainCount %@",[array1 valueForKey:@"retainCount"]);
NSLog(@"array1 %p",array1);
for (int i =0; i.count; i++) {
Person *person = array1[i];
NSLog(@"person:%p",person);
}
NSLog(@"***********");
NSLog(@"array2 %p",array2);
for (int i =0; i.count; i++) {
Person *person = array2[i];
NSLog(@"person:%p",person);
}
NSLog(@"***********");
运行结果如下:
2017-05-01 10:36:29.352 interviewDemo[15539:2798778] array1 retainCount (
2,
2
)
2017-05-01 10:36:29.353 interviewDemo[15539:2798778] array1 0x600000424780
2017-05-01 10:36:29.353 interviewDemo[15539:2798778] person:0x600000424b40
2017-05-01 10:36:29.353 interviewDemo[15539:2798778] person:0x600000424440
2017-05-01 10:36:29.353 interviewDemo[15539:2798778] ***********
2017-05-01 10:36:29.353 interviewDemo[15539:2798778] array2 0x600000423f00
2017-05-01 10:36:29.354 interviewDemo[15539:2798778] person:0x600000424ae0
2017-05-01 10:36:29.354 interviewDemo[15539:2798778] person:0x6000004240a0
2017-05-01 10:36:29.354 interviewDemo[15539:2798778] ***********
array1,array2指向的内存地址不一样,array1,array2内的元素指向的内存地址不一样。实现了真正意义上的其内元素对象进行了复制。