面試題分解—「淺複製/深複製、定義屬性使用copy還是strong ?」

不知名開發者發表於2018-09-27

引導


關於淺複製和深複製的概念,讓我感覺有點繞口,以及定義NSString是使用copy還是使用strong那?花費一天的時間,我對這模組做了概念理解和程式碼驗證(有詳細的分析過程),最後總結了這篇文章。由眾-不知名開發者,原創文章。對內容有疑問可留言交流。

  1. 對以下內容驗證
  2. 對isa指標簡單釋義一下;
  3. 淺複製和深複製的概念徹底理解;
  4. 非集合類(NSString)物件進行copy、mutableCopy操作;
  5. 面試題:為什麼定義NSString要使用copy而不建議使用strong,使用strong會有什麼影響❓
  6. 集合類物件(NSArray)進行copy、mutableCopy操作,單層深複製和真正深複製,集合類內部包含物件是指標複製;
  7. 面試題:定義NSArray型別的屬性,把修飾詞copy換成strong有什麼影響❓
  8. 面試題:定義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.呼叫allocretain方法後,引用計數值加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) - 0x7ffeebf79a78NSLog(@"%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,驗證檢視。

  1. 每一個物件本質上都是一個類的例項。其中類定義了成員變數和成員方法的列表。物件通過物件的isa指標指向所屬類。
  2. 每一個類本質上都是一個物件,類其實是元類(meteClass)的例項。元類定義了類方法的列表。類通過類的isa指標指向元類。
  3. 元類儲存了類方法的列表。當類方法被呼叫時,先會從本身查詢類方法的實現,如果沒有,元類會向他父類查詢該方法。同時注意的是:元類(meteClass)也是類,它也是物件。元類通過isa指標最終指向的是一個根元類(root meteClass)。
  4. 根元類的isa指標指向本身,這樣形成了一個封閉的內迴圈。
  • 2.問題:怎麼判斷物件copy和mutableCopy後返回的是不可變型別還是可變型別❓ 分析:看到這樣一個結論,在runtime下NSString的真實型別是"__NSCFConstantString",而NSMutableString的真實型別是"__NSCFString"。通過上面對NSString和NSMutableString列印結果,根據NSString初始化方式不同,列印的物件真實型別有"__NSCFConstantString"和"__NSCFString"都存在的情況。

淺複製和深複製


相關概念釋義
  1. 非集合類物件指的是NSString,NSNumber等物件。 集合類物件指的是NSArray,NSDictionary,NSSet等物件。

  2. 在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'

  3. 淺複製和深複製

  • 1.淺複製:只複製了物件的指標,指標指向的記憶體地址還是和源物件指標指向的記憶體地址一樣,物件的引用計數+1;
  • 2.深複製:即複製了物件的指標,也複製了指標指向的記憶體地址,生成新的指標和記憶體地址且引用計數為+1,對源物件的引用計數沒有影響;

4、圖解複製概念

面試題分解—「淺複製/深複製、定義屬性使用copy還是strong ?」

非集合類物件的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型別的屬性,修飾詞也可以使用strongcopy的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使用copycopy修飾後的物件生成了新的記憶體地址,為深複製。對新生成的物件進行appendString:操作會發生crash,說明其實copy修飾後返回的是不可變型別。
        2.定義NSMutableString使用strongstrong修飾後物件的記憶體地址和原物件的記憶體地址相同,為淺複製,可以進行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型別的屬性,指向不可變型別,copystrong修飾均沒有生成新的記憶體地址,只是複製了物件的指標且引用計數+1copy修飾為淺複製;
 --------
 場景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 和 先歸檔再解檔操作,均生成了新的記憶體地址,都為深複製,
 --------
 場景40x10766a230// 原陣列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'

整體總結一下:不管是非集合類還是集合類

淺複製深複製示例.png

  • 對於不可變物件,使用copy是淺複製(指向不可變物件為淺複製;指向可變物件時生成了新的記憶體地址,為深複製),使用mutableCopy是深複製
  • 對於可變物件,不管使用copy或者mutableCopy都是深複製

Reading


  • 各位廠友,由於「時間 & 知識」有限,總結的文章難免有「未全、不足」,該模組將系統化學習,後替換、補充文章內容 ~
  • 熬夜寫者不易,不知名開發者 學會交流和分享。

相關文章