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

引導


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

  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代碼。下面很形象一示例來釋義什麼是對象的引用計數。git

照明對比引用計數 對照明設備所作的動做 對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方法中廢棄對象。github

引用計數示例代碼
說明:
    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,驗證查看。app

  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等對象。ide

  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,對源對象的引用計數沒有影響;

四、圖解複製概念 ui

非集合類對象的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會讓其共用同一地址來節省內存的開銷。但中若是出現了中文,則會另外開闢新的內存空間進行存儲。

總結:對於不可變對象
一、retain/strong/copy,都會使原對象的引用計數+1,指針指向對象的內存地址和原對象同樣; 二、copy操做爲淺複製,沒有生成新的內存地址;mutableCopy操做爲深複製,生成了新的內存地址,新生成的對象引用計數爲1; 三、修改原對象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的狀況)

總結:對可變對象 一、strong、retain會使原對象的引用計數+1,指針指向的內存地址和原對象仍是同樣的;copy、mutableCopy對原對象的引用計數沒有影響,使新生成的對象的引用計數爲1; 二、進行操做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有什麼影響❓ 分析: 一、指向不可變對象,外界修改原對象array,這裏原對象自己爲不可變對象,對它進行修改,系統又從新分配了內存地址與_arrayCopy的內存地址不同,因此只有原對象改變。 二、指向可變對象,外界修改原對象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


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