引導
關於淺複製和深複製的概念,讓我感覺有點繞口,以及定義NSString是使用copy還是使用strong那?花費一天的時間,我對這模組做了概念理解和程式碼驗證(有詳細的分析過程),最後總結了這篇文章。由眾-不知名開發者,原創文章。對內容有疑問可留言交流。
- 對以下內容驗證
- 對isa指標簡單釋義一下;
- 淺複製和深複製的概念徹底理解;
- 非集合類(NSString)物件進行copy、mutableCopy操作;
- 面試題:為什麼定義NSString要使用copy而不建議使用strong,使用strong會有什麼影響❓
- 集合類物件(NSArray)進行copy、mutableCopy操作,單層深複製和真正深複製,集合類內部包含物件是指標複製;
- 面試題:定義NSArray型別的屬性,把修飾詞copy換成strong有什麼影響❓
- 面試題:定義NSMutableArray型別的屬性,修飾詞把strong換成copy有什麼影響❓
引用計數
引用計數釋義
如今進入ARC的屎蛋,就無需再次輸入retain
或者release
程式碼。下面很形象一示例來釋義什麼是物件的引用計數。
照明對比引用計數 | 對照明裝置所做的動作 | 對oc物件所做的動作 | 引用技數 | oc方法 |
---|---|---|---|---|
第一個進入辦公室的人 | 開燈 | 生成並持有物件 | 0 --- >1 | alloc,new,copy,mutableCopy等方法 |
之後每當有人進入辦公室 | 需要照明 | 持有物件 | 1 --- >2 | retain方法 |
每當有人下班離開辦公室 | 不需要照明 | 釋放物件 | 2 --- > 1 | release方法 |
最後一個人下班離開辦公室 | 關燈 | 廢棄物件 | 1 ----> 0 | dealloc方法 |
總結:
1.在oc的物件中存有引用計數這一整數值。
2.呼叫alloc
或retain
方法後,引用計數值加1。
3.呼叫release
後,引用計數值減1。
4.引用計數值為0時,呼叫dealloc
方法廢棄物件。
5.當然不管引用計數是否為0,你也可以主動在dealloc
方法中廢棄物件。
引用計數示例程式碼
說明:
1.以下所有示例程式碼列印用的Log巨集,列印:記憶體地址,指標,真實型別,引用計數,值
#define LNLog(description,obj) NSLog(@"%@: 記憶體地址:%p, 指標地址:%p, 真實型別:%@, 引用計數:%lu, 值:%@", (description),(obj),&(obj),(obj).class,(unsigned long)(obj).retainCount,(obj));
2.列印retainCount
NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)obj));//ARC_橋接字方式
NSLog(@"Retain count %@", [obj valueForKey:@"retainCount"]);//ARC_kvc方式
NSLog(@"retain count = %ld",obj.retainCount);//MRC
3.列印物件地址有兩種情況
物件指標的地址;列印方式:`NSLog(@"%p",&b) - 0x7ffeebf79a78、NSLog(@"%x",&b) - ebf79a78`;
指標指向物件的記憶體地址(即儲存的內容);列印方式:`NSLog(@"%p",b) - 0x60400022b740`;
- (void)testSringOrMutableStringRetainCount
{
// 不可變字串建立幾種方式
NSString * str1 = @"PublicCoderLN";
LNLog(@"直接複製", str1);
NSString * str2 = [NSString stringWithString:@"PublicCoderLN"];//會有警告
LNLog(@"WithString1", str2);
NSString * str3 = [[NSString alloc] initWithString:@"PublicCoderLN"];
LNLog(@"WithString2", str3);
NSString * str4 = [NSString stringWithFormat:@"PublicCoderLN"];
LNLog(@"WithFormat1", str4);
NSString * str5 = [[NSString alloc] initWithFormat:@"PublicCoderLN"];
LNLog(@"WithFormat2", str5);
/
列印:
直接複製: 記憶體地址:0x104516290, 指標地址:0x7ffeeb6eba78, 真實型別:__NSCFConstantString, 引用計數:18446744073709551615, 值:PublicCoderLN
WithString1: 記憶體地址:0x104516290, 指標地址:0x7ffeeb6eba70, 真實型別:__NSCFConstantString, 引用計數:18446744073709551615, 值:PublicCoderLN
WithString2: 記憶體地址:0x104516290, 指標地址:0x7ffeeb6eba68, 真實型別:__NSCFConstantString, 引用計數:18446744073709551615, 值:PublicCoderLN
WithFormat1: 記憶體地址:0x604000235dc0, 指標地址:0x7ffeeb6eba60, 真實型別:__NSCFString, 引用計數:1, 值:PublicCoderLN
WithFormat2: 記憶體地址:0x604000237f00, 指標地址:0x7ffeeb6eba58, 真實型別:__NSCFString, 引用計數:1, 值:PublicCoderLN
*/
NSMutableString * strM1 = [[NSMutableString alloc] initWithString:@"PublicCoderLN"];
LNLog(@"strM1", strM1);
NSMutableString * strM2 = [[NSMutableString alloc] initWithFormat:@"PublicCoderLN"];
LNLog(@"strM2", strM2);
/
列印:
strM1: 記憶體地址:0x6040004412c0, 指標地址:0x7ffeeb6eba50, 真實型別:__NSCFString, 引用計數:1, 值:PublicCoderLN
strM2: 記憶體地址:0x6040004413b0, 指標地址:0x7ffeeb6eba48, 真實型別:__NSCFString, 引用計數:1, 值:PublicCoderLN
*/
}
複製程式碼
總結:
- 1.NSString前三種建立出來的isa是NSCFConstantString,引用計數是個無限大的數,如果用int型別列印引用計數都為-1,這表示__NSCFConstantString不會被釋放.
- 2.後兩種和建立其他object是一樣的在堆中佔有記憶體,引用計數為1;
註解:
- 1.這裡對isa指標簡單釋義一下 runtime.h
isa:是一個Class 型別的指標。可Xcode(cmd+shift+O)快捷搜尋objc.h-Line38 和 runtime.h-Line55,驗證檢視。
- 每一個物件本質上都是一個類的例項。其中類定義了成員變數和成員方法的列表。物件通過物件的isa指標指向所屬類。
- 每一個類本質上都是一個物件,類其實是元類(meteClass)的例項。元類定義了類方法的列表。類通過類的isa指標指向元類。
- 元類儲存了類方法的列表。當類方法被呼叫時,先會從本身查詢類方法的實現,如果沒有,元類會向他父類查詢該方法。同時注意的是:元類(meteClass)也是類,它也是物件。元類通過isa指標最終指向的是一個根元類(root meteClass)。
- 根元類的isa指標指向本身,這樣形成了一個封閉的內迴圈。
- 2.問題:怎麼判斷物件copy和mutableCopy後返回的是不可變型別還是可變型別❓ 分析:看到這樣一個結論,在runtime下NSString的真實型別是"__NSCFConstantString",而NSMutableString的真實型別是"__NSCFString"。通過上面對NSString和NSMutableString列印結果,根據NSString初始化方式不同,列印的物件真實型別有"__NSCFConstantString"和"__NSCFString"都存在的情況。
淺複製和深複製
相關概念釋義
-
非集合類物件指的是NSString,NSNumber等物件。 集合類物件指的是NSArray,NSDictionary,NSSet等物件。
-
在Objective-C中,必須遵守
<NSCopying, NSMutableCopying>
,才能通過兩個方法 copy和mutableCopy可以執行拷貝操作,其中copy是獲得一個不可變物件,而mutableCopy是獲得一個可變物件。並且兩個方法分別呼叫copyWithZone和mutableCopyWithZone兩個方法來進行拷貝操作。如果對copy返回物件使用mutable物件介面就會crash,或者強制呼叫這兩個方法會發生crash,如:對UILabel(繼承UIView,只遵守了NSCopying協議)進行mutableCopy操作,結果會報reason: '-[UILabel mutableCopyWithZone:]: unrecognized selector sent to instance 0x7fbaecf1e880'
。 -
淺複製和深複製
- 1.淺複製:只複製了物件的指標,指標指向的記憶體地址還是和源物件指標指向的記憶體地址一樣,物件的引用計數+1;
- 2.深複製:即複製了物件的指標,也複製了指標指向的記憶體地址,生成新的指標和記憶體地址且引用計數為+1,對源物件的引用計數沒有影響;
4、圖解複製概念
非集合類物件的copy與mutableCopy
定義NSString型別的屬性(copy/strong修飾詞)指向不可變物件
@property (nonatomic, copy) NSString * stringCopy;
@property (nonatomic, strong) NSString * stringStrong;
- (void)testStringUseRetainOrCopyOrMutableCopy
{
NSString * string = [NSString stringWithFormat:@"publicCoderLN"];
LNLog(@"originString", string);
NSLog(@"--------");
// 場景1:定義NSString型別的屬性(copy/strong修飾詞)指向不可變物件.
self.stringCopy = string;
LNLog(@"_stringCopy", _stringCopy);
self.stringStrong = string;
LNLog(@"_stringStrong", _stringStrong);
NSLog(@"--------");
// 場景2:對不可變型別NSString,進行retain、copy、mutableCopy操作
NSString * strRetain1 = [string retain];
LNLog(@"strRetain1", strRetain1);
NSString * strCopy1 = [string copy];
LNLog(@"strCopy1", strCopy1);
NSString * strMCopy1 = [string mutableCopy];
LNLog(@"strMCopy1", strMCopy1);
/
列印:
originString: 記憶體地址:0x60400003e320, 指標地址:0x7ffee5e6ba78, 真實型別:__NSCFString, 引用計數:1, 值:publicCoderLN
_stringCopy: 記憶體地址:0x60400003e320, 指標地址:0x7fa7bd609770, 真實型別:__NSCFString, 引用計數:2, 值:publicCoderLN
_stringStrong: 記憶體地址:0x60400003e320, 指標地址:0x7fa7bd609778, 真實型別:__NSCFString, 引用計數:3, 值:publicCoderLN
--------
strRetain1: 記憶體地址:0x60400003e320, 指標地址:0x7ffee5e6ba70, 真實型別:__NSCFString, 引用計數:4, 值:publicCoderLN
strCopy1: 記憶體地址:0x60400003e320, 指標地址:0x7ffee5e6ba68, 真實型別:__NSCFString, 引用計數:5, 值:publicCoderLN
strMCopy1: 記憶體地址:0x600000251e50, 指標地址:0x7ffee5e6ba60, 真實型別:__NSCFString, 引用計數:1, 值:publicCoderLN
*/
}
複製程式碼
分析: 1.從列印結果可以看出,copy、strong、retain均使物件的retainCount +1; 2.copy不可變物件,只是複製了物件的指標,和原物件string指標地址不同(文中其實做了2次copy),但指標指向物件的記憶體地址還是同一個,所以是淺複製; 3.mutableCopy不可變物件,即複製了物件的指標,也複製了指標指向物件的記憶體地址,生成了新的指標和新的記憶體地址,所以是深複製; 4.提醒:這裡的顯示地址不是不變的,地址是系統分配的,可能你程式碼驗證的時候就不是這裡顯示的地址了,但概念是對的;
// 場景3:定義NSString型別的屬性,修飾詞用把copy換成strong後,再修改原物件有什麼影響❓
string = [string substringToIndex:3];
LNLog(@"originString", string);
LNLog(@"_stringCopy", _stringCopy);
LNLog(@"_stringStrong", _stringStrong);
LNLog(@"strRetain1", strRetain1);
LNLog(@"strCopy1", strCopy1);
LNLog(@"strMCopy1", strMCopy1);
/
列印:
originString: 記憶體地址:0xa000000006275703, 指標地址:0x7ffee5e6ba78, 真實型別:NSTaggedPointerString, 引用計數:18446744073709551615, 值:pub【改原物件】
_stringCopy: 記憶體地址:0x60400003e320, 指標地址:0x7fa7bd609770, 真實型別:__NSCFString, 引用計數:5, 值:publicCoderLN
_stringStrong: 記憶體地址:0x60400003e320, 指標地址:0x7fa7bd609778, 真實型別:__NSCFString, 引用計數:5, 值:publicCoderLN
- - -
strRetain1: 記憶體地址:0x60400003e320, 指標地址:0x7ffee5e6ba70, 真實型別:__NSCFString, 引用計數:5, 值:publicCoderLN
strCopy1: 記憶體地址:0x60400003e320, 指標地址:0x7ffee5e6ba68, 真實型別:__NSCFString, 引用計數:5, 值:publicCoderLN
strMCopy1: 記憶體地址:0x600000251e50, 指標地址:0x7ffee5e6ba60, 真實型別:__NSCFString, 引用計數:1, 值:publicCoderLN
分析:
1、原物件string的型別本身就是不可變型別,再對其進行操作,系統自動分配了新的記憶體地址(只是物件的指標和沒有修改前原物件的指標一樣),這裡對原物件string操作後生成的記憶體地址和其他型別(copy/strong/retain)都不一樣,所以只有原物件string操作後改變。
2、【提醒】:有廠友這時候可能會說,從上面修改前和修改後的列印結果來看,我定義NSString使用copy修飾和使用strong,記憶體地址和值都是一樣的,那我為什麼不使用strong修飾NSString那❓
分析:一些情況下從效能考慮定義不可變NSString型別的屬性,修飾詞也可以使用strong。
copy的set方法內部會對傳入的字串進行判斷是不可變的還是可變的,如果是不可變字串,就直接給屬性進行賦值,不會生成新的記憶體地址;
如果是可變字串對string屬性賦值,會進行Copy操作,重新生成一份新的記憶體地址。
這裡會對copy修飾的string會進行判斷,幾個不會影響效能,但是如果很多就會有了吧。
開發中很多都是定死的NSString不可變字串的,使用strong也可以。
// copy的set方法
- (void)setStringCopy:(NSString *)stringCopy
{
_stringCopy = [stringCopy copy];
}
*/
複製程式碼
註解:
上面列印出現了NSTaggedPointerString
型別,想更多瞭解的可參考採用Tagged Pointer的字串。
簡單點說,在字串小於長度12且為immutable物件時,Apple會讓其共用同一地址來節省記憶體的開銷。但中如果出現了中文,則會另外開闢新的記憶體空間進行儲存。
總結:對於不可變物件
1、retain/strong/copy,都會使原物件的引用計數+1,指標指向物件的記憶體地址和原物件一樣;
2、copy操作為淺複製,沒有生成新的記憶體地址;mutableCopy操作為深複製,生成了新的記憶體地址,新生成的物件引用計數為1;
3、修改原物件string的值,對copy和strong修飾定義的屬性目標物件_stringCopy 沒有影響,還是原來的值(記憶體地址和值都是原來的);
定義NSString型別的屬性(copy/strong修飾詞)指向可變物件
@property (nonatomic, copy) NSMutableString * stringMCopy;
@property (nonatomic, strong) NSMutableString * stringMStrong;
- (void)testMStringUseRetainOrCopyOrMutableCopy
{
NSMutableString * stringM = [NSMutableString stringWithFormat:@"%@", @"publicCoderLN"];
LNLog(@"originString", stringM);
NSLog(@"--------");
// 場景1:定義NSString型別的屬性(copy/strong修飾詞)指向可變物件.
self.stringCopy = stringM;
LNLog(@"_stringCopy", _stringCopy);
self.stringStrong = stringM;
LNLog(@"_stringStrong", _stringStrong);
NSLog(@"--------");
// 場景2:對可變型別NSMutableString,進行retain、copy、mutableCopy操作
NSMutableString * stringMRetain = [stringM retain];
LNLog(@"stringMRetain", stringMRetain);
NSMutableString * stringMCopy = [stringM copy];
LNLog(@"stringMCopy", stringMCopy);
NSMutableString * stringMMutableCopy = [stringM mutableCopy];
LNLog(@"stringMMutableCopy", stringMMutableCopy);
/
列印:
originString: 記憶體地址:0x604000245cd0, 指標地址:0x7ffee1d38a78, 真實型別:__NSCFString, 引用計數:1, 值:publicCoderLN
_stringCopy: 記憶體地址:0x604000236da0, 指標地址:0x7f94d4603980, 真實型別:__NSCFString, 引用計數:1, 值:publicCoderLN
_stringStrong: 記憶體地址:0x604000245cd0, 指標地址:0x7f94d4603988, 真實型別:__NSCFString, 引用計數:2, 值:publicCoderLN
--------
stringMRetain: 記憶體地址:0x604000245cd0, 指標地址:0x7ffee1d38a70, 真實型別:__NSCFString, 引用計數:3, 值:publicCoderLN
stringMCopy: 記憶體地址:0x60000003b080, 指標地址:0x7ffee1d38a68, 真實型別:__NSCFString, 引用計數:1, 值:publicCoderLN
stringMMutableCopy: 記憶體地址:0x600000456860, 指標地址:0x7ffee1d38a60, 真實型別:__NSCFString, 引用計數:1, 值:publicCoderLN
--------
分析:
1、定義NSString型別的屬性(copy/strong修飾詞)指向可變物件時,可以看到copy修飾的生成了新的記憶體地址。
2、對可變物件stringM進行操作,retain操作引用計數+1,而copy/mutableCopy操作均產生了新指標和新的指標指向的記憶體地址,都為深複製,。
*/
NSLog(@"--------");
// 場景4:定義NSString型別的屬性,修飾詞用把copy換成strong後,再修改原物件有什麼影響❓
[stringM appendString:@"+CoderLN"];
LNLog(@"originString", stringM);
LNLog(@"_stringCopy", _stringCopy);
LNLog(@"_stringStrong", _stringStrong);
LNLog(@"stringMRetain", stringMRetain);
LNLog(@"stringMCopy", stringMCopy);
LNLog(@"stringMMutableCopy", stringMMutableCopy);
/
列印:
originString: 記憶體地址:0x604000245cd0, 指標地址:0x7ffee1d38a78, 真實型別:__NSCFString, 引用計數:3, 值:publicCoderLN+CoderLN【改原物件】
_stringCopy: 記憶體地址:0x604000236da0, 指標地址:0x7f94d4603980, 真實型別:__NSCFString, 引用計數:1, 值:publicCoderLN
_stringStrong: 記憶體地址:0x604000245cd0, 指標地址:0x7f94d4603988, 真實型別:__NSCFString, 引用計數:3, 值:publicCoderLN+CoderLN
- - -
stringMRetain: 記憶體地址:0x604000245cd0, 指標地址:0x7ffee1d38a70, 真實型別:__NSCFString, 引用計數:3, 值:publicCoderLN+CoderLN
stringMCopy: 記憶體地址:0x60000003b080, 指標地址:0x7ffee1d38a68, 真實型別:__NSCFString, 引用計數:1, 值:publicCoderLN
stringMMutableCopy: 記憶體地址:0x600000456860, 指標地址:0x7ffee1d38a60, 真實型別:__NSCFString, 引用計數:1, 值:publicCoderLN
分析:
1.上面copy修飾的指向可變物件,生成了新的記憶體地址,而strong修飾的和原物件stringM的記憶體地址一樣,所以原物件的修改,strong修飾的也被改變了。
2.對NSMutableString屬性進行copy、mutableCopy操作,均生成了新的記憶體地址(指向另一個物件),都為深複製,所以原物件改變不會對他們有影響;
*/
}
複製程式碼
面試題:為什麼定義NSString要使用copy而不建議使用strong❓ 分析:將物件宣告為NSString不可變型別時,都不希望它改變(外界修改了,不影響自身的值),從上面列印結果可以看出,strong修飾的NSString型別屬性遇到賦值可變型別時,修改原物件的值,_stringStrong也同樣改變了,而copy修飾的string的值沒有改變。 回答:定義NSString使用copy,為了防止遇到把一個可變字串在未使用copy方法時賦值給這個字串物件後,再修改原字串時(可變字元),本字串也會被修改的情況發生(就用strong的情況)。
總結:對可變物件 1、strong、retain會使原物件的引用計數+1,指標指向的記憶體地址和原物件還是一樣的;copy、mutableCopy對原物件的引用計數沒有影響,使新生成的物件的引用計數為1; 2、進行操作copy、mutableCopy均為深複製,即複製了物件的指標,也複製了指標指向的記憶體地址;
定義NSMutableString型別的屬性,如果把修飾詞strong換成copy有什麼影響。
@property (nonatomic, copy) NSMutableString * stringMCopy;
@property (nonatomic, strong) NSMutableString * stringMStrong;
- (void)testMutableStringCopyOrStrong
{
NSMutableString *stringM = [[NSMutableString alloc] initWithFormat:@"%@",@"publicCoderLN"];
LNLog(@"stringM", stringM);
self.stringMCopy = stringM;
LNLog(@"_stringMCopy", _stringMCopy);
self.stringMStrong = stringM;
LNLog(@"_stringMStrong", _stringMStrong);
// 面試題:定義NSMutableString型別的屬性,如果把修飾詞strong換成copy有什麼影響❓
//[self.stringMCopy appendString:@"+CoderLN"];// 會crash
//[self.stringMStrong appendString:@"+CoderLN"];// strong修飾的正常執行.
//[[self.stringMCopy mutableCopy] appendString:@"+CoderLN"];// 返回的可變型別.
/
列印:
stringM: 記憶體地址:0x60400025f2f0, 指標地址:0x7ffee4502a78, 真實型別:__NSCFString, 引用計數:1, 值:publicCoderLN
_stringMCopy: 記憶體地址:0x60000022bda0, 指標地址:0x7f7f90525de0, 真實型別:__NSCFString, 引用計數:1, 值:publicCoderLN
_stringMStrong: 記憶體地址:0x60400025f2f0, 指標地址:0x7f7f90525de8, 真實型別:__NSCFString, 引用計數:2, 值:publicCoderLN
總結:
1.定義NSMutableString使用copy,copy修飾後的物件生成了新的記憶體地址,為深複製。對新生成的物件進行appendString:操作會發生crash,說明其實copy修飾後返回的是不可變型別。
2.定義NSMutableString使用strong,strong修飾後物件的記憶體地址和原物件的記憶體地址相同,為淺複製,可以進行appendString:操作,說明其實strong修飾後返回的還是是可變型別。
3.回答:定義NSMutableString使用修飾詞strong換成copy,如果對copy後的不可變型別,再進行可變的修改操作,就會造成崩潰。
*/
}
複製程式碼
集合類物件的copy與mutableCopy
定義NSArray型別的屬性(copy/strong修飾詞)指向不可變物件
@property (nonatomic ,copy) NSArray *arrayCopy;
@property (nonatomic ,strong) NSArray *arrayStrong;
- (void)testArrayUseCopyOrMutableCopy
{
NSArray *array = @[@"A", @"B"];
LNLog(@"originAry", array);
NSLog(@"--------");
// 場景1:定義NSArray型別的屬性(copy|strong修飾)指向不可變物件
self.arrayCopy = array;
LNLog(@"_arrayCopy", _arrayCopy);
self.arrayStrong = array;
LNLog(@"_arrayStrong", _arrayStrong);
NSLog(@"--------");
// 場景2:對不可變型別陣列,進行copy|mutableCopy操作
NSArray * arrayCopy = [array copy];// 淺
LNLog(@"arrayCopy", arrayCopy);
NSMutableArray * arrayMCopy = [array mutableCopy];// 深
LNLog(@"arrayMCopy", arrayMCopy);
NSLog(@"--------");
// 場景3:對陣列不可變型別,進行淺深複製
NSArray * arrayCopy1 = [array copyWithZone:nil];// 淺
LNLog(@"arrayCopy1", arrayCopy1);
NSArray * arrayCopy2 = [[NSArray alloc] initWithArray:array copyItems:NO];// 淺
LNLog(@"arrayCopy2", arrayCopy2);
NSArray * arrayCopy3 = [[NSArray alloc] initWithArray:array copyItems:YES];// 單層深複製
LNLog(@"arrayCopy3", arrayCopy3);
// 真正的深複製:先歸檔再解檔
NSArray * arrayCopy4 = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:array]];
LNLog(@"arrayCopy4", arrayCopy4);
NSLog(@"--------");
// 場景4:驗證陣列中的元素均為指標複製
NSLog(@"%p",array[0]);
NSLog(@"%p",arrayCopy[0]);
NSLog(@"%p",arrayMCopy[0]);
NSLog(@"--------");
// 面試題:定義NSArray型別的屬性,把修飾詞copy換成strong有什麼影響❓
// 指向不可變物件,外界修改原物件array
array = [array arrayByAddingObject:@"Public-CoderLN"];
LNLog(@"originArray", array);
LNLog(@"_arrayCopy", _arrayCopy);
LNLog(@"_arrayStrong", _arrayStrong);
NSLog(@"--------");
// 場景6:定義NSArray型別的屬性(copy|strong修飾詞)指向可變物件
NSMutableArray * arrayM = [[NSMutableArray alloc] initWithArray:@[@"a",@"b"]];
self.arrayCopy = arrayM;
self.arrayStrong = arrayM;
// 指向可變物件,外界修改原物件arrayM
[arrayM removeAllObjects];
LNLog(@"originArrayM", arrayM);
LNLog(@"_arrayCopy", _arrayCopy);
LNLog(@"_arrayStrong", _arrayStrong);
}
複製程式碼
/
列印:
originAry: 記憶體地址:0x604000037f00, 指標地址:0x7ffee8599a50, 真實型別:__NSArrayI, 引用計數:1, 值:(A,B)
--------
場景1:
_arrayCopy: 記憶體地址:0x604000037f00, 指標地址:0x7fa41771edf0, 真實型別:__NSArrayI, 引用計數:2, 值:(A,B)
_arrayStrong: 記憶體地址:0x604000037f00, 指標地址:0x7fa41771edf8, 真實型別:__NSArrayI, 引用計數:3, 值:(A,B)
分析:定義NSArray型別的屬性,指向不可變型別,copy和strong修飾均沒有生成新的記憶體地址,只是複製了物件的指標且引用計數+1,copy修飾為淺複製;
--------
場景2:
arrayCopy: 記憶體地址:0x604000037f00, 指標地址:0x7ffee8599a48, 真實型別:__NSArrayI, 引用計數:4, 值:(A,B)
rrayMCopy: 記憶體地址:0x60000044c090, 指標地址:0x7ffee8599a40, 真實型別:__NSArrayM, 引用計數:1, 值:(A,B)
分析:對原物件array進行Copy操作,沒有生成新的記憶體地址且引用計數+1,為淺複製;進行mutableCopy操作,生成了新的記憶體地址,所以為深複製。
--------
場景3:
arrayCopy1: 記憶體地址:0x604000037f00, 指標地址:0x7ffee8599a38, 真實型別:__NSArrayI, 引用計數:5, 值:(A,B)
arrayCopy2: 記憶體地址:0x604000037f00, 指標地址:0x7ffee8599a30, 真實型別:__NSArrayI, 引用計數:6, 值:(A,B)
arrayCopy3: 記憶體地址:0x6000004200c0, 指標地址:0x7ffee8599a28, 真實型別:__NSArrayI, 引用計數:1, 值:(A,B)
arrayCopy4: 記憶體地址:0x604000038060, 指標地址:0x7ffee8599a20, 真實型別:__NSArrayI, 引用計數:1, 值:(A,B)
分析:對原物件array進行 copyWithZone:和initWithArray:copyItems:引數為NO 沒有生成新的記憶體地址,都為淺複製,
對原物件array進行 initWithArray:copyItems:引數為YES 和 先歸檔再解檔操作,均生成了新的記憶體地址,都為深複製,
--------
場景4:
0x10766a230// 原陣列array[0]
0x10766a230// arrayCopy[0]
0x10766a230// arrayMCopy[0]
分析:列印陣列中的元素記憶體地址都是同一個,不管是對原陣列做copy/mutableCopy操作,陣列中的元素均為淺複製。
--------
【面試題】:定義NSArray型別的屬性,把修飾詞copy換成strong有什麼影響❓
//指向不可變物件,外界修改原物件array
originArray: 記憶體地址:0x60000044c060, 指標地址:0x7ffee8599a50, 真實型別:__NSArrayI, 引用計數:1, 值:(A,B,"Public-CoderLN")【改原物件】
_arrayCopy: 記憶體地址:0x604000037f00, 指標地址:0x7fa41771edf0, 真實型別:__NSArrayI, 引用計數:6, 值:(A,B)
_arrayStrong: 記憶體地址:0x604000037f00, 指標地址:0x7f9395d0a6f8, 真實型別:__NSArrayI, 引用計數:6, 值:(A,B)
--------
//指向可變物件,外界修改原物件arrayM
originArrayM: 記憶體地址:0x60000044c1b0, 指標地址:0x7ffeea6c7a18, 真實型別:__NSArrayM, 引用計數:2, 值:( )
_arrayCopy: 記憶體地址:0x600000420160, 指標地址:0x7fa41771edf0, 真實型別:__NSArrayI, 引用計數:1, 值:(a,b)
_arrayStrong: 記憶體地址:0x60000044c1b0, 指標地址:0x7fa41771edf8, 真實型別:__NSArrayM, 引用計數:2, 值:( )
*/
複製程式碼
面試題:定義NSArray型別的屬性,把修飾詞copy換成strong有什麼影響❓ 分析: 1、指向不可變物件,外界修改原物件array,這裡原物件本身為不可變物件,對它進行修改,系統又重新分配了記憶體地址與_arrayCopy的記憶體地址不一樣,所以只有原物件改變。 2、指向可變物件,外界修改原物件arrayM,這裡原物件本身為可變物件,對它進行修改,由於copy修飾做了深複製生成了新的記憶體地址,而strong修飾記憶體地址和原物件arrayM相同,所以原物件改變,strong修飾的也跟著改變了。 回答:定義NSArray使用copy,為了防止遇到把一個可變陣列在未使用copy方法時賦值給這個物件後,再修改原陣列時,這個物件也會被修改的情況發生(就如strong的情況會被修改)。
定義NSMutableArray型別的屬性(copy/strong修飾詞)指向可變物件
@property (nonatomic ,copy) NSMutableArray *arrayMCopy;
@property (nonatomic ,strong) NSMutableArray *arrayMStrong;
- (void)testMutableArrayUseCopyOrMutableCopy
{
NSMutableArray * arrayM = [NSMutableArray arrayWithArray:@[@"A",@"B"]];
LNLog(@"originAry", arrayM);
// 場景1:給定義NSMutableArray型別的屬性(copy/strong修飾詞)賦值可變物件
self.arrayMCopy = arrayM;
LNLog(@"_arrayCopy", _arrayCopy);
self.arrayMStrong = arrayM;
LNLog(@"_arrayMStrong", _arrayMStrong);
NSLog(@"--------");
// 場景2:對可變型別陣列,進行copy、mutableCopy操作
NSArray * arrayC1 = [arrayM copy];
LNLog(@"arrayC1", arrayC1);
NSArray * arrayC2 = [arrayM mutableCopy];
LNLog(@"arrayC2", arrayC2);
NSMutableArray * arrayM1 = [arrayM copy];
LNLog(@"arrayM1", arrayM1);
NSMutableArray * arrayM2 = [arrayM mutableCopy];
LNLog(@"arrayM2", arrayM2);
NSLog(@"--------");
// 場景3:NSCopying和NSMutableCopying
NSMutableArray * shallowCopyArrayM = [arrayM mutableCopyWithZone:nil];// 淺複製
LNLog(@"shallowCopyArrayM", shallowCopyArrayM);
NSMutableArray * deepCopyArrayM1 = [[NSMutableArray alloc] initWithArray:arrayM copyItems:NO];
LNLog(@"deepCopyArrayM1", deepCopyArrayM1);
NSMutableArray * deepCopyArrayM2 = [[NSMutableArray alloc] initWithArray:arrayM copyItems:YES];
LNLog(@"deepCopyArrayM2", deepCopyArrayM2);
// 面試題:定義NSMutableArray型別的屬性,修飾詞把strong換成copy有什麼影響❓
//[self.arrayMCopy removeLastObject];// 會crash
[self.arrayMStrong removeLastObject];// 正常執行
}
/
列印:
originAry: 記憶體地址:0x604000253740, 指標地址:0x7ffee591da60, 真實型別:__NSArrayM, 引用計數:1, 值:(A,B)
_arrayCopy: 記憶體地址:0x0, 指標地址:0x7fa029f2b400, 真實型別:(null), 引用計數:0, 值:(null)
_arrayMStrong: 記憶體地址:0x604000253740, 指標地址:0x7fa029f2b418, 真實型別:__NSArrayM, 引用計數:2, 值:(A,B)
--------
arrayC1: 記憶體地址:0x60000022d780, 指標地址:0x7ffee591da58, 真實型別:__NSArrayI, 引用計數:1, 值:(A,B)
arrayC2: 記憶體地址:0x6000004573d0, 指標地址:0x7ffee591da50, 真實型別:__NSArrayM, 引用計數:1, 值:(A,B)
arrayM1: 記憶體地址:0x60000022d680, 指標地址:0x7ffee591da48, 真實型別:__NSArrayI, 引用計數:1, 值:(A,B)
arrayM2: 記憶體地址:0x600000457520, 指標地址:0x7ffee591da40, 真實型別:__NSArrayM, 引用計數:1, 值:(A,B)
--------
shallowCopyArrayM: 記憶體地址:0x604000253a70, 指標地址:0x7ffee591da38, 真實型別:__NSArrayM, 引用計數:1, 值:(A,B)
deepCopyArrayM1: 記憶體地址:0x604000253d10, 指標地址:0x7ffee591da30, 真實型別:__NSArrayM, 引用計數:1, 值:(A,B)
deepCopyArrayM2: 記憶體地址:0x600000457430, 指標地址:0x7ffee591da28, 真實型別:__NSArrayM, 引用計數:1, 值:(A,B)
分析:
1.對NSMutableArray型別的屬性,copy和mutableCopy操作都是深複製。
*/
複製程式碼
面試題:定義NSMutableArray型別的屬性,修飾詞把strong換成copy有什麼影響❓
回答:定義NSMutableArray型別的屬性,修飾詞用把strong換成copy,遇到可變物件賦值,再對_arrayMCopy做可變操作會崩潰,因為copy後返回的是不可變型別。reason: '-[__NSArrayI removeLastObject]: unrecognized selector sent to instance 0x600000426f00'
整體總結一下:不管是非集合類還是集合類
- 對於不可變物件,使用copy是淺複製
(指向不可變物件為淺複製;指向可變物件時生成了新的記憶體地址,為深複製)
,使用mutableCopy是深複製。 - 對於可變物件,不管使用copy或者mutableCopy都是深複製。
Reading
- 各位廠友,由於「時間 & 知識」有限,總結的文章難免有「未全、不足」,該模組將系統化學習,後替換、補充文章內容 ~
- 熬夜寫者不易,不知名開發者 學會交流和分享。