在面试iOS程序员的时候,大家经常被问到的一个问题就是,在定义一个NSString类型的属性时,为什么要用copy修饰?通常得到的回答都是,
“为了防止修改这个属性时,会同时修改了原对象的值。”
我不知道这个结论他们是怎么得出来的,我只想说,错,错极了。
下面我就给大家解释下为什么那么回答是错的,直接上代码。(文章最后有如何正确地回答这个问题,着急的同学可以直接翻到最后)
定义4个属性
nameStrong为不用copy修饰的情况,nameCopy为用copy修饰的情况。normalName和mutableName为两种原字符串。
@property (nonatomic, strong) NSString *nameStrong; // 用strong修饰
@property (nonatomic, copy) NSString *nameCopy; // 用copy修饰
@property (nonatomic, copy) NSString *normalName; // 原字符串-不可变
@property (nonatomic, strong) NSMutableString *mutableName; // 原字符串-可变
原字符串为不可变的情况
先说原字符串为不可变的情况,这种情况比较简单。
给normalName赋值,并把normalName赋值给nameStrong和nameCopy,如下
self.normalName = @"1111";
self.nameStrong = self.normalName;
self.nameCopy = self.normalName;
NSLog(@"\nnormalName: %@ - normalName地址: %p\nnameStrong: %@ - nameStrong地址: %p\nnameCopy: %@ - nameCopy地址: %p",
self.normalName, _normalName, self.nameStrong, _nameStrong, self.nameCopy, _nameCopy);
打印结果为:
normalName: 1111 - normalName地址: 0x10c1a3220
nameStrong: 1111 - nameStrong地址: 0x10c1a3220
nameCopy: 1111 - nameCopy地址: 0x10c1a3220
你会发现,nameStrong和nameCopy同原字符串的地址是一样的,所以值肯定也是一样的。
如果对原字符串normalName进行改变呢?严谨来说,normalName为不可变类型,只能重新进行赋值,如下:
self.normalName = @"1111";
self.nameStrong = self.normalName;
self.nameCopy = self.normalName;
NSLog(@"\nnormalName: %@ - normalName地址: %p\nnameStrong: %@ - nameStrong地址: %p\nnameCopy: %@ - nameCopy地址: %p",
self.normalName, _normalName, self.nameStrong, _nameStrong, self.nameCopy, _nameCopy);
self.normalName = @"2222";
NSLog(@"\nnormalName: %@ - normalName地址: %p\nnameStrong: %@ - nameStrong地址: %p\nnameCopy: %@ - nameCopy地址: %p",
self.normalName, _normalName, self.nameStrong, _nameStrong, self.nameCopy, _nameCopy);
打印结果为:
normalName: 1111 - normalName地址: 0x101a07220
nameStrong: 1111 - nameStrong地址: 0x101a07220
nameCopy: 1111 - nameCopy地址: 0x101a07220
normalName: 2222 - normalName地址: 0x101a07260
nameStrong: 1111 - nameStrong地址: 0x101a07220
nameCopy: 1111 - nameCopy地址: 0x101a07220
你会发现,nameStrong和nameCopy的地址并没有发生变化,还是同最初normalName的地址是一样的,所以值没变,但normalName重新赋值后,地址发生了变化,指针指向了一块新的地址。
结论:如果原字符串为不可变类型字符串,使用copy或strong修饰NSString效果是一样的。
原字符串为可变的情况
之所以面试中会有这个问题,主要就是考虑到有这种情况。
给mutableName进行赋值,并把mutableName赋值给nameStrong和nameCopy,如下:
self.mutableName = [NSMutableString stringWithString:@"1111"];
self.nameStrong = self.mutableName;
self.nameCopy = self.mutableName;
NSLog(@"\nmutableName: %@ - mutableName地址: %p\nnameStrong: %@ - nameStrong地址: %p\nnameCopy: %@ - nameCopy地址: %p",
self.mutableName, _mutableName, self.nameStrong, _nameStrong, self.nameCopy, _nameCopy);
打印结果为:
mutableName: 1111 - mutableName地址: 0x604000258900
nameStrong: 1111 - nameStrong地址: 0x604000258900
nameCopy: 1111 - nameCopy地址: 0xa000000313131314
你会发现,三个属性的值是一样的,但nameStrong同mutableName指向的是同一块地址,而nameCopy则是指向的一块新的地址。那是因为在把mutableName赋值给nameCopy时,自动进行了深拷贝,把mutableName的内容复制了一份,并新开了一块内存来存储,然后让nameCopy指向了这个新的地址。
如果对mutableName进行改变呢?如下:
self.mutableName = [NSMutableString stringWithString:@"1111"];
self.nameStrong = self.mutableName;
self.nameCopy = self.mutableName;
NSLog(@"\nmutableName: %@ - mutableName地址: %p\nnameStrong: %@ - nameStrong地址: %p\nnameCopy: %@ - nameCopy地址: %p",
self.mutableName, _mutableName, self.nameStrong, _nameStrong, self.nameCopy, _nameCopy);
[self.mutableName appendString:@"aaaa"];
NSLog(@"\nmutableName: %@ - mutableName地址: %p\nnameStrong: %@ - nameStrong地址: %p\nnameCopy: %@ - nameCopy地址: %p",
self.mutableName, _mutableName, self.nameStrong, _nameStrong, self.nameCopy, _nameCopy);
打印结果为:
mutableName: 1111 - mutableName地址: 0x6000004453a0
nameStrong: 1111 - nameStrong地址: 0x6000004453a0
nameCopy: 1111 - nameCopy地址: 0xa000000313131314
mutableName: 1111aaaa - mutableName地址: 0x6000004453a0
nameStrong: 1111aaaa - nameStrong地址: 0x6000004453a0
nameCopy: 1111 - nameCopy地址: 0xa000000313131314
你会发现,nameStrong的值也被改变了,但nameCopy并没改变。这就是为什么要使用copy的原因了。
如果是直接对mutableName进行赋值操作,则同normalName一样,对strongName和copyName都不会有影响,只是改变的它自己,如下:
self.mutableName = [NSMutableString stringWithString:@"1111"];
self.nameStrong = self.mutableName;
self.nameCopy = self.mutableName;
NSLog(@"\nmutableName: %@ - mutableName地址: %p\nnameStrong: %@ - nameStrong地址: %p\nnameCopy: %@ - nameCopy地址: %p",
self.mutableName, _mutableName, self.nameStrong, _nameStrong, self.nameCopy, _nameCopy);
// [self.mutableName appendString:@"aaaa"];
self.mutableName = [NSMutableString stringWithString:@"2222"];
NSLog(@"\nmutableName: %@ - mutableName地址: %p\nnameStrong: %@ - nameStrong地址: %p\nnameCopy: %@ - nameCopy地址: %p",
self.mutableName, _mutableName, self.nameStrong, _nameStrong, self.nameCopy, _nameCopy);
打印结果为:
mutableName: 1111 - mutableName地址: 0x60400024e970
nameStrong: 1111 - nameStrong地址: 0x60400024e970
nameCopy: 1111 - nameCopy地址: 0xa000000313131314
mutableName: 2222 - mutableName地址: 0x60400024edf0
nameStrong: 1111 - nameStrong地址: 0x60400024e970
nameCopy: 1111 - nameCopy地址: 0xa000000313131314
同normalName情况一样,不再解释。
现在又有一个新的问题,如果为了防止自己被改变,必须要用copy修饰吗?其实不是,你也可以用strong修饰它,但赋值的时候记得调用一下copy
方法,比如我给nameStrong赋值时,调用copy
方法,如下:
self.mutableName = [NSMutableString stringWithString:@"1111"];
self.nameStrong = [self.mutableName copy];
self.nameCopy = self.mutableName;
NSLog(@"\nmutableName: %@ - mutableName地址: %p\nnameStrong: %@ - nameStrong地址: %p\nnameCopy: %@ - nameCopy地址: %p",
self.mutableName, _mutableName, self.nameStrong, _nameStrong, self.nameCopy, _nameCopy);
打印结果为:
mutableName: 1111 - mutableName地址: 0x60000044ef40
nameStrong: 1111 - nameStrong地址: 0xa000000313131314
nameCopy: 1111 - nameCopy地址: 0xa000000313131314
你会发现,nameStrong的地址同mutableName的地址也不一样了,nameStrong和nameCopy指向了同一块新的地址。
虽然使用这种方式可以解决使用strong修饰的问题,但一般情况下定义属性时直接使用copy修饰更方便。
结论
那应该怎么回答这个问题呢?我认为可以这样回答:
为了防止在把一个可变字符串在未使用copy方法时赋值给这个字符串对象时,修改原字符串时,本字符串也会被动进行修改的情况发生。
Have fun!