mutableCopy与copy在面试中的那些坑你知道吗?

来源:互联网 发布:爱丁堡商学院申请 知乎 编辑:程序博客网 时间:2024/06/11 00:14

  最近听到朋友谈论在面试中被面试官通过基础知识深挖狂虐的事情,心中有些不忿,决定推出一系列基础知识重温的文章,在方便自己复习的同时,希望和大家一块进步。
  这一篇文章主要对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 0x60000003e8002017-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 0x60800002fd202017-04-30 18:12:17.995 interviewDemo[14122:2595610] jack.child 0x60800002fd202017-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 0x608000037be02017-04-30 18:15:26.720 interviewDemo[14164:2598814] jack.child 0x60800003e2802017-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<array1.count; i++) {        Person *person = array1[i];        NSLog(@"person:%p",person);    }    NSLog(@"***********");    NSLog(@"array2 %p",array2);    for (int i =0; i<array2.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 0x600000226d402017-05-01 08:42:32.994 interviewDemo[14956:2719378] person:0x600000226d802017-05-01 08:42:32.994 interviewDemo[14956:2719378] person:0x600000226d602017-05-01 08:42:32.994 interviewDemo[14956:2719378] ***********2017-05-01 08:42:32.994 interviewDemo[14956:2719378] array2 0x600000226d402017-05-01 08:42:32.995 interviewDemo[14956:2719378] person:0x600000226d802017-05-01 08:42:32.995 interviewDemo[14956:2719378] person:0x600000226d602017-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<array1.count; i++) {        Person *person = array1[i];        NSLog(@"person:%p",person);    }    NSLog(@"***********");    NSLog(@"array2 %p",array2);    for (int i =0; i<array2.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 0x6080000339202017-05-01 09:29:24.575 interviewDemo[15094:2757902] person:0x6080000336002017-05-01 09:29:24.575 interviewDemo[15094:2757902] person:0x6080000335e02017-05-01 09:29:24.575 interviewDemo[15094:2757902] ***********2017-05-01 09:29:24.576 interviewDemo[15094:2757902] array2 0x6080000339202017-05-01 09:29:24.576 interviewDemo[15094:2757902] person:0x6080000336002017-05-01 09:29:24.576 interviewDemo[15094:2757902] person:0x6080000335e02017-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<array1.count; i++) {        Person *person = array1[i];        NSLog(@"person:%p",person);    }    NSLog(@"***********");    NSLog(@"array2 %p",array2);    for (int i =0; i<array2.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 0x6000002224002017-05-01 10:07:37.823 interviewDemo[15227:2771155] person:0x6000002221602017-05-01 10:07:37.823 interviewDemo[15227:2771155] person:0x6000002223602017-05-01 10:07:37.823 interviewDemo[15227:2771155] ***********2017-05-01 10:07:37.823 interviewDemo[15227:2771155] array2 0x6000002451c02017-05-01 10:07:37.823 interviewDemo[15227:2771155] person:0x6000002221602017-05-01 10:07:37.823 interviewDemo[15227:2771155] person:0x6000002223602017-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<array1.count; i++) {        Person *person = array1[i];        NSLog(@"person:%p",person);    }    NSLog(@"***********");    NSLog(@"array2 %p",array2);    for (int i =0; i<array2.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 0x60800042dc202017-05-01 10:11:49.070 interviewDemo[15256:2774681] person:0x60800022c9202017-05-01 10:11:49.070 interviewDemo[15256:2774681] person:0x6080002227402017-05-01 10:11:49.071 interviewDemo[15256:2774681] ***********2017-05-01 10:11:49.071 interviewDemo[15256:2774681] array2 0x6080000534d02017-05-01 10:11:49.071 interviewDemo[15256:2774681] person:0x60800022c9202017-05-01 10:11:49.071 interviewDemo[15256:2774681] person:0x6080002227402017-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<array1.count; i++) {        Person *person = array1[i];        NSLog(@"person:%p",person);    }    NSLog(@"***********");    NSLog(@"array2 %p",array2);    for (int i =0; i<array2.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 0x6000004247802017-05-01 10:36:29.353 interviewDemo[15539:2798778] person:0x600000424b402017-05-01 10:36:29.353 interviewDemo[15539:2798778] person:0x6000004244402017-05-01 10:36:29.353 interviewDemo[15539:2798778] ***********2017-05-01 10:36:29.353 interviewDemo[15539:2798778] array2 0x600000423f002017-05-01 10:36:29.354 interviewDemo[15539:2798778] person:0x600000424ae02017-05-01 10:36:29.354 interviewDemo[15539:2798778] person:0x6000004240a02017-05-01 10:36:29.354 interviewDemo[15539:2798778] ***********

array1,array2指向的内存地址不一样,array1,array2内的元素指向的内存地址不一样。实现了真正意义上的其内元素对象进行了复制。

2 0
原创粉丝点击