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

  最近听到朋友谈论在面试中被面试官通过基础知识深挖狂虐的事情,心中有些不忿,决定推出一系列基础知识重温的文章,在方便自己复习的同时,希望和大家一块进步。
  这一篇文章主要对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影响,是一个我们预期意外的值,不能做为参考使用。
下面是我从网上找来的一张图片
mutableCopy与copy在面试中的那些坑你知道吗?_第1张图片

  根据苹果官方的定义,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内的元素指向的内存地址不一样。实现了真正意义上的其内元素对象进行了复制。

你可能感兴趣的:(Object-C,面试题)