在面試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!