深復(fù)制、淺復(fù)制、copy、mutableCopy

1. 屬性中copystrong特性的區(qū)別

在開(kāi)始學(xué)習(xí)淺復(fù)制(Shallow Copy)、深復(fù)制(Deep Copy)之前,先了解下屬性中copystrong特性的區(qū)別。

copy特性如下:

  • copy:創(chuàng)建一個(gè)對(duì)象的副本。在創(chuàng)建的那一刻新對(duì)象與原始對(duì)象內(nèi)容相同。
  • 新的對(duì)象引用計(jì)數(shù)為1,與原始對(duì)象引用計(jì)數(shù)無(wú)關(guān),且原始對(duì)象引用計(jì)數(shù)不會(huì)改變。
  • 使用copy創(chuàng)建的新對(duì)象也是強(qiáng)引用,使用完成后需要負(fù)責(zé)釋放該對(duì)象。
  • copy特性可以減少對(duì)象對(duì)上下文的依賴(lài)。新對(duì)象、原始對(duì)象中任一對(duì)象的值改變不會(huì)影響另一對(duì)象的值。
  • 要想設(shè)置該對(duì)象的特性為copy,該對(duì)象必須遵守NSCopying協(xié)議,F(xiàn)oundation類(lèi)默認(rèn)實(shí)現(xiàn)了NSCopying協(xié)議,所以只需要為自定義的類(lèi)實(shí)現(xiàn)該協(xié)議即可。

strong特性如下:

  • 創(chuàng)建一個(gè)強(qiáng)引用的指針,引用對(duì)象引用計(jì)數(shù)加1。
  • strong特性表示兩個(gè)對(duì)象內(nèi)存地址相同(建立一個(gè)指針,進(jìn)行指針拷貝),內(nèi)容會(huì)一直保持相同,直到更改一方內(nèi)存地址,或?qū)⑵湓O(shè)置為nil。
  • 如果有多個(gè)對(duì)象同時(shí)引用一個(gè)屬性,任一對(duì)象對(duì)該屬性的修改都會(huì)影響其他對(duì)象獲取的值。

如果想要對(duì)屬性中特性進(jìn)行更全面了解,可以查看 iOS中定義屬性時(shí)的atomic、nonatomic、copy、assign、strong、weak等幾個(gè)特性的區(qū)別 這篇文章。

2. 淺復(fù)制與深復(fù)制

對(duì)象的拷貝有淺復(fù)制和深復(fù)制兩種方式。淺復(fù)制只復(fù)制指向?qū)ο蟮闹羔?,并不?fù)制對(duì)象本身;深復(fù)制是直接復(fù)制整個(gè)對(duì)象到另一塊內(nèi)存中。即淺復(fù)制是復(fù)制指針,深復(fù)制是復(fù)制內(nèi)容。

NSObject提供了copymutableCopy 方法,copy復(fù)制后對(duì)象是不可變對(duì)象(immutable),mutableCopy復(fù)制后的對(duì)象是可變對(duì)象(mutable),與原始對(duì)象是否可變無(wú)關(guān)。

下面針對(duì)非集合類(lèi)、集合類(lèi)對(duì)象的深復(fù)制、淺復(fù)制進(jìn)行說(shuō)明。

2.1 非集合類(lèi)對(duì)象的copymutableCopy

非集合類(lèi)對(duì)象指的是NSString、NSNumber之類(lèi)的對(duì)象,深復(fù)制會(huì)復(fù)制引用對(duì)象的內(nèi)容,而淺復(fù)制只復(fù)制引用這些對(duì)象的指針。因此,如果對(duì)象A被淺復(fù)制到對(duì)象B,對(duì)象B和對(duì)象A引用的是同一內(nèi)存地址的實(shí)例變量或?qū)傩浴?/p>

CopyObjectCopying.png

2.1.1 不可變對(duì)象的copymutableCopy

創(chuàng)建一個(gè)Single View Application模板的demo,demo名稱(chēng)為copy&mutableCopy。進(jìn)入ViewController.m,在實(shí)現(xiàn)部分添加以下方法。

// 非容器類(lèi) 不可變對(duì)象
- (void)immutableObject {
    // 1.創(chuàng)建一個(gè)string字符串。
    NSString *string = @"github.com/pro648";
    NSString *stringB = string;
    NSString *stringCopy = [string copy];
    NSMutableString *stringMutableCopy = [string mutableCopy];
    
    // 2.輸出指針指向的內(nèi)存地址。
    NSLog(@"Memory location of string = %p",string);
    NSLog(@"Memory location of stringB = %p",stringB);
    NSLog(@"Memory location of stringCopy = %p",stringCopy);
    NSLog(@"Memory location of stringMutableCopy = %p",stringMutableCopy);
}

上述代碼分步說(shuō)明如下:

  1. 創(chuàng)建一個(gè)string字符串,之后通過(guò)賦值,調(diào)用copy、mutableCopy方法進(jìn)行復(fù)制操作。
  2. 通過(guò)使用%p,輸出指針?biāo)赶騼?nèi)容的內(nèi)存地址。

然后在viewDidLoad中調(diào)用該方法:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 1.非容器類(lèi) 不可變對(duì)象
    [self immutableObject];
}

運(yùn)行demo,可以看到控制臺(tái)輸出如下:

Memory location of string = 0x1060e6068
Memory location of stringB = 0x1060e6068
Memory location of stringCopy = 0x1060e6068
Memory location of stringMutableCopy = 0x600000072000

可以看到,stringstringBstringCopy內(nèi)存地址一致,即指向的是同一塊內(nèi)存區(qū)域,進(jìn)行了淺復(fù)制操作。而stringMutableCopy與另外三個(gè)變量?jī)?nèi)存地址不同,系統(tǒng)為其分配了新內(nèi)存,即進(jìn)行了深復(fù)制操作。

2.1.2 可變對(duì)象的copymutableCopy

繼續(xù)在實(shí)現(xiàn)部分添加以下方法,并記得在viewDidLoad中調(diào)用。

// 2.非容器類(lèi) 可變對(duì)象
- (void)mutableObject {
    // 1.創(chuàng)建一個(gè)可變字符串。
    NSMutableString *mString = [NSMutableString stringWithString:@"github.com/pro648"];
    NSString *mStringCopy = [mString copy];
    NSMutableString *mutablemString = [mString copy];
    NSMutableString *mStringMutableCopy = [mString mutableCopy];
    
    // 2.在可變字符串后添加字符串。
    [mString appendString:@"AA"];
    [mutablemString appendString:@"BB"];  // 運(yùn)行時(shí),這一行會(huì)報(bào)錯(cuò)。
    [mStringMutableCopy appendString:@"CC"];
    
    // 3.輸出指針指向的內(nèi)存地址。
    NSLog(@"Memory location of \n mString = %p,\n mstringCopy = %p,\n mutablemString = %p,\n mStringMutableCopy = %p",mString, mStringCopy, mutablemString, mStringMutableCopy);
}

在上面代碼中,注釋2部分為可變字符串拼接字符串,運(yùn)行到為mutablemString拼接字符串這一行代碼時(shí),程序會(huì)崩潰,因?yàn)橥ㄟ^(guò)copy方法獲得的字符串是不可變字符串。所以在運(yùn)行前要注釋掉這一行。

運(yùn)行demo,可以看到控制臺(tái)輸出如下:

Memory location of 
 mString = 0x60000007bc00,
 mstringCopy = 0x600000051940,
 mutablemString = 0x6000000517c0,
 mStringMutableCopy = 0x60000007bec0

可以看到四個(gè)對(duì)象內(nèi)存地址各不相同。所以,這里的copymutableCopy執(zhí)行的均為深復(fù)制。

綜合上面兩個(gè)例子,我們可以得出這樣結(jié)論:

  • 對(duì)不可變對(duì)象執(zhí)行copy操作,是指針復(fù)制,執(zhí)行mutableCopy操作是內(nèi)容復(fù)制。
  • 對(duì)可變對(duì)象執(zhí)行copy操作和mutableCopy操作都是內(nèi)容復(fù)制。

用代碼表示如下:

[immutableObject copy];         // 淺復(fù)制
[immutableObject mutableCopy];          // 深復(fù)制
[mutableObject copy];           // 深復(fù)制
[mutableObject mutableCopy] ;   // 深復(fù)制

2.2 容器類(lèi)對(duì)象的深復(fù)制、淺復(fù)制

容器類(lèi)對(duì)象指NSArray、NSDictionary等。容器類(lèi)對(duì)象的深復(fù)制、淺復(fù)制如下圖所示:

CopyCollectionCopy.png

對(duì)于容器類(lèi),需要探討的是復(fù)制后容器內(nèi)元素的變化,而非容器本身內(nèi)存地址是否發(fā)生了變化。

2.2.1 容器類(lèi)對(duì)象的淺復(fù)制

有許多方法可以對(duì)集合進(jìn)行淺復(fù)制。當(dāng)對(duì)集合進(jìn)行淺復(fù)制時(shí),將復(fù)制原始集合中元素指針到新的集合,即原始集合中元素引用計(jì)數(shù)加一。

<a id="YES">

在實(shí)現(xiàn)部分添加以下方法,并在viewDidLoad中調(diào)用該方法。

// 3.淺復(fù)制容器類(lèi)對(duì)象。
- (void)shallowCopyCollections {
    // 1.創(chuàng)建一個(gè)不可變數(shù)組,數(shù)組內(nèi)元素為可變字符串。
    NSMutableString *red = [NSMutableString stringWithString:@"Red"];
    NSMutableString *green = [NSMutableString stringWithString:@"Green"];
    NSMutableString *blue = [NSMutableString stringWithString:@"Blue"];
    NSArray *myArray1 = [NSArray arrayWithObjects:red, green, blue, nil];
    
    // 2.進(jìn)行淺復(fù)制。
    NSArray *myArray2 = [myArray1 copy];
    NSMutableArray *myMutableArray3 = [myArray1 mutableCopy];
    NSArray *myArray4 = [[NSArray alloc] initWithArray:myArray1 copyItems:NO];
    
    // 3.修改myArray2的第一個(gè)元素。
    NSMutableString *tempString = myArray2.firstObject;
    [tempString appendString:@"Color"];
    
    // 4.輸出四個(gè)數(shù)組內(nèi)存地址及四個(gè)數(shù)組內(nèi)容。
    NSLog(@"Memory location of \n myArray1 = %p, \n myArray2 %p, \n myMutableArray3 %p, \n myArray4 %p",myArray1, myArray2, myMutableArray3, myArray4);
    NSLog(@"Contents of \n myArray1 %@, \n myArray2 %@, \n myMutableArray3 %@, \n myArray4 %@",myArray1, myArray2, myMutableArray3, myArray4);
}

</a>

運(yùn)行demo,可以看到控制臺(tái)輸出如下:

Memory location of 
 myArray1 = 0x60800004f240, 
 myArray2 0x60800004f240, 
 myMutableArray3 0x60800004ef40, 
 myArray4 0x60800004f090
Contents of 
 myArray1 (
    RedColor,
    Green,
    Blue
), 
 myArray2 (
    RedColor,
    Green,
    Blue
), 
 myMutableArray3 (
    RedColor,
    Green,
    Blue
), 
 myArray4 (
    RedColor,
    Green,
    Blue
)

可以看到myArray1myArray2數(shù)組內(nèi)存地址相同,myMutableArray3myArray4與其它數(shù)組內(nèi)存地址各不相同。這是因?yàn)?code>mutableCopy的對(duì)象會(huì)被分配新的內(nèi)存,alloc會(huì)為對(duì)象分配新的內(nèi)存空間。

觀(guān)察數(shù)組內(nèi)元素,發(fā)現(xiàn)修改myArray2數(shù)組內(nèi)第一個(gè)元素,四個(gè)數(shù)組第一個(gè)元素都發(fā)生了改變,所以這里只進(jìn)行了淺復(fù)制。

2.2.2 容器類(lèi)對(duì)象的深復(fù)制

有兩種方式對(duì)容器類(lèi)對(duì)象進(jìn)行深復(fù)制:

  • 第一種方法是:使用initWithArray: copyItems:類(lèi)型方法,其中,第二個(gè)參數(shù)為YES。
  • 第二種方法是:使用歸檔、解檔。

下面先看如何使用initWithArray: copyItems:類(lèi)型方法。使用該方法進(jìn)行深復(fù)制時(shí),第二個(gè)參數(shù)為YES。如果使用該方法對(duì)集合進(jìn)行深復(fù)制,那么集合內(nèi)每個(gè)元素都會(huì)收到copyWithZone: 消息,我們平常使用copymutableCopy方法時(shí),系統(tǒng)會(huì)把copymutableCopy自動(dòng)替換為copyWithZone:mutableCopyWithZone:。即copymutableCopy只是簡(jiǎn)便方法。如果集合內(nèi)元素遵守NSCopying協(xié)議,元素被復(fù)制到新的集合。如果集合內(nèi)元素不遵守NSCopying協(xié)議,用這樣的方式進(jìn)行深復(fù)制,會(huì)在運(yùn)行時(shí)產(chǎn)生錯(cuò)誤。

copyWithZone: 產(chǎn)生的是淺復(fù)制,所以,這種方法只能產(chǎn)生一層深復(fù)制 one-level-deep copy,如果集合內(nèi)元素仍然是集合,則子集合內(nèi)元素不會(huì)被深復(fù)制,只對(duì)子集合內(nèi)元素指針進(jìn)行復(fù)制。

如果集合內(nèi)元素為不可變對(duì)象,發(fā)送copyWithZone:消息后進(jìn)行指針復(fù)制,該對(duì)象仍然不可變,因此只進(jìn)行指針復(fù)制即可滿(mǎn)足需求。

如果集合內(nèi)元素為可變對(duì)象,發(fā)送copyWithZone:消息后進(jìn)行的是內(nèi)容復(fù)制,復(fù)制后該元素不可變,此時(shí),完成了一層深復(fù)制。

上面代碼注釋2部分中,initWithArray: copyItems:第二個(gè)參數(shù)修改為YES,注釋4中輸出部分修改為輸出數(shù)組第一個(gè)元素內(nèi)存地址。更新后如下:

// 4.容器類(lèi)一層深復(fù)制
- (void)oneLevelDeepCopy {
    // 1.創(chuàng)建一個(gè)不可變數(shù)組,數(shù)組內(nèi)元素為可變字符串。
    NSMutableString *red = [NSMutableString stringWithString:@"Red"];
    NSMutableString *green = [NSMutableString stringWithString:@"Green"];
    NSMutableString *blue = [NSMutableString stringWithString:@"Blue"];
    NSArray *myArray1 = [NSArray arrayWithObjects:red, green, blue, nil];
    
    // 2.進(jìn)行淺復(fù)制。
    NSArray *myArray2 = [myArray1 copy];
    NSMutableArray *myMutableArray3 = [myArray1 mutableCopy];
    NSArray *myArray4 = [[NSArray alloc] initWithArray:myArray1 copyItems:YES];
    
    // 3.修改myArray2的第一個(gè)元素。
    NSMutableString *tempString = myArray2.firstObject;
    [tempString appendString:@"Color"];
    
    // 4.輸出數(shù)組內(nèi)第一個(gè)元素內(nèi)存地址,輸出四個(gè)數(shù)組。
    NSLog(@"Memory location of \n myArray1.firstObject = %p, \n myArray2.firstObject %p, \n myMutableArray3.firstObject %p, \n myArray4.firstObject %p",myArray1.firstObject, myArray2.firstObject, myMutableArray3.firstObject, myArray4.firstObject);
    NSLog(@"Contents of \n myArray1 %@, \n myArray2 %@, \n myMutableArray3 %@, \n myArray4 %@",myArray1, myArray2, myMutableArray3, myArray4);
}

運(yùn)行demo,可以看到控制臺(tái)輸出如下:

Memory location of 
 myArray1.firstObject = 0x600000079980, 
 myArray2.firstObject 0x600000079980, 
 myMutableArray3.firstObject 0x600000079980, 
 myArray4.firstObject 0xa000000006465523
Contents of 
 myArray1 (
    RedColor,
    Green,
    Blue
), 
 myArray2 (
    RedColor,
    Green,
    Blue
), 
 myMutableArray3 (
    RedColor,
    Green,
    Blue
), 
 myArray4 (
    Red,
    Green,
    Blue
)

可以看到myArray4數(shù)組內(nèi)第一個(gè)元素與其它數(shù)組第一個(gè)元素內(nèi)存地址不同,即進(jìn)行了一層深復(fù)制。

這種對(duì)集合進(jìn)行深復(fù)制的方法,對(duì)其它類(lèi)型集合也有效。如詞典中initWithDictionary: withItems:方法。

如果你的數(shù)組內(nèi)元素是另一個(gè)數(shù)組,想要進(jìn)行完全深復(fù)制,可以使用歸檔、解歸檔方法。使用該方法時(shí),歸檔對(duì)象要遵守NSCoding協(xié)議。如果你對(duì)歸檔不熟悉,可以查看我的另一篇文章:數(shù)據(jù)存儲(chǔ)之歸檔解檔 NSKeyedArchiver NSKeyedUnarchiver。

下面使用歸檔、解檔的方法進(jìn)行完全深復(fù)制。

// 5.使用歸檔進(jìn)行完全深復(fù)制。
- (void)trueDeepCopy {
    // 1.創(chuàng)建一個(gè)可變數(shù)組,數(shù)組第一個(gè)元素是另一個(gè)可變數(shù)組,第二個(gè)元素是另一個(gè)不可變數(shù)組。
    NSMutableString *hue = [NSMutableString stringWithString:@"hue"];
    NSMutableString *saturation = [NSMutableString stringWithString:@"saturation"];
    NSMutableString *brightness = [NSMutableString stringWithString:@"brightness"];
    NSMutableArray *hsbArray1 = [NSMutableArray arrayWithObjects:hue, saturation, brightness, nil];
    NSArray *hsbArray2 = [NSArray arrayWithObjects:hue, saturation, brightness, nil];
    NSMutableArray *hsbArray3 = [NSMutableArray arrayWithObjects:hsbArray1, hsbArray2, nil];
    
    // 2.通過(guò)歸檔、解檔進(jìn)行完全深復(fù)制。
    NSData *dataArea = [NSKeyedArchiver archivedDataWithRootObject:hsbArray3];
    NSMutableArray *hsbArray4 = [NSKeyedUnarchiver unarchiveObjectWithData:dataArea];
    
    // 3.輸出hsbArray3和hsbArray4數(shù)組第一個(gè)元素內(nèi)存地址。
    NSLog(@"Memory location of \n hsbArray3.firstObject = %p, \n hsbArray4.firstObject = %p",hsbArray3.firstObject, hsbArray4.firstObject);
}

上面代碼中,可變數(shù)組hsbArray3第一個(gè)元素是可變數(shù)組hsbArray1,第二個(gè)元素是不可變數(shù)組hsbArray2。

使用歸檔、讀取歸檔方法深復(fù)制后,在控制臺(tái)輸出hsbArray3hsbArray4第一個(gè)元素內(nèi)存地址。輸出如下:

Memory location of 
 hsbArray3.firstObject = 0x60000004b100, 
 hsbArray4.firstObject = 0x60000004b1f0

可以看到hsbArray3hsbArray4數(shù)組內(nèi)元素內(nèi)存地址不同,即進(jìn)行了一層深復(fù)制。

trueDeepCopy方法內(nèi),繼續(xù)為hsbArray4數(shù)組內(nèi)第一個(gè)元素tempArray1可變數(shù)組添加字符串對(duì)象。為hsbArray4第二個(gè)元素hsbArray2數(shù)組添加字符串對(duì)象。最后輸出hsbArray3hsbArray4數(shù)組內(nèi)容。

// 5.使用歸檔進(jìn)行完全深復(fù)制。
- (void)trueDeepCopy {
    ...
    // 4.為hsbArray4第一個(gè)元素添加字符串。
    NSMutableArray *tempArray1 = hsbArray4.firstObject;
    [tempArray1 addObject:@"hsb"];
    
    // 5.hsbArray4第二個(gè)元素是hsbArray2,而hsbArray2是不可變數(shù)組,這一步將產(chǎn)生錯(cuò)誤。
//    NSMutableArray *tempArray2 = hsbArray4[1];
//    [tempArray2 addObject:@"Color"];
    
    // 6.輸出數(shù)組內(nèi)容。
    NSLog(@"Contents of \n hsbArray3 %@, \n hsbArray4 %@",hsbArray3, hsbArray4);
}

因?yàn)?code>hsbArray4第二個(gè)元素是hsbArray2副本,而hsbArray2是不可變數(shù)組,這一步將產(chǎn)生錯(cuò)誤。注釋掉5部分代碼后,控制臺(tái)輸出如下:

Contents of 
 hsbArray3 (
        (
        hue,
        saturation,
        brightness
    ),
        (
        hue,
        saturation,
        brightness
    )
), 
 hsbArray4 (
        (
        hue,
        saturation,
        brightness,
        hsb
    ),
        (
        hue,
        saturation,
        brightness
    )
)

可以看到只有hsbArray4數(shù)組第一個(gè)元素內(nèi)對(duì)象發(fā)生了改變,所以,使用歸檔、讀取歸檔進(jìn)行的是完全深復(fù)制。

復(fù)制集合時(shí),該集合、集合內(nèi)元素的可變性可能會(huì)受到影響。每種方法對(duì)任意深度集合中對(duì)象的可變性有稍微不同的影響。

  1. copyWithZone:創(chuàng)建對(duì)象的最外層 surface level不可變,所有更深層次對(duì)象的可變性不變。
  2. mutableCopyWithZone:創(chuàng)建對(duì)象的最外層 surface level可變,所有更深層次對(duì)象的可變性不變。
  3. initWithArray: copyItems:第二個(gè)參數(shù)為NO,此時(shí),所創(chuàng)建數(shù)組最外層可變性與初始化的可變性相同,所有更深層級(jí)對(duì)象可變性不變。
  4. initWithArray: copyItems:第二個(gè)參數(shù)為YES,此時(shí),所創(chuàng)建數(shù)組最外層可變性與初始化的可變性相同,下一層級(jí)是不可變的,所有更深層級(jí)對(duì)象可變性不變。
  5. 歸檔、解檔復(fù)制的集合,所有層級(jí)的可變性與原始對(duì)象相同。

2.3 自定義對(duì)象的深復(fù)制、淺復(fù)制

自定義的類(lèi)需要我們自己實(shí)現(xiàn)NSCopying、NSMutableCopying協(xié)議,這樣才可以調(diào)用copymutableCopy方法。

添加父類(lèi)為NSObject,名稱(chēng)為Person的類(lèi)。進(jìn)入Person.h,添加以下屬性和方法,同時(shí)讓該類(lèi)遵守NSCopying協(xié)議。

@interface Person : NSObject <NSCopying>

@property (strong, nonatomic) NSString *name;
@property (assign, nonatomic) NSUInteger age;

- (void)setName:(NSString *)name withAge:(NSUInteger)age;

@end

NSCopying協(xié)議只有一個(gè)必須實(shí)現(xiàn)的copyWithZone:方法。進(jìn)入Person.m,實(shí)現(xiàn)屬性中setName: withAge:方法和copyWithZone:方法。

- (void)setName:(NSString *)name withAge:(NSUInteger)age {
    _name = name;
    _age = age;
}

- (id)copyWithZone:(NSZone *)zone {
    Person *person = [[Person allocWithZone:zone] init];
    [person setName:self.name withAge:self.age];
    return person;
}

如果Person類(lèi)會(huì)被繼承,那么copyWithZone:方法將被繼承,這時(shí)應(yīng)將上面的

Person *person = [[Person allocWithZone:zone] init];

替換為

id person = [[[self class] allocWithZone: zone] init];

這樣,可以從該類(lèi)分配一個(gè)新對(duì)象,而這個(gè)類(lèi)是copy的接收者。例如:如果Person類(lèi)有一個(gè)名為NewPerson的子類(lèi),那么應(yīng)該在繼承的方法中分配了新的NewPerson對(duì)象,而不是Person對(duì)象。

如果Person類(lèi)的父類(lèi)也實(shí)現(xiàn)了NSCopying協(xié)議,那么應(yīng)該先調(diào)用父類(lèi)的copy方法,以復(fù)制繼承來(lái)的實(shí)例變量。如果需要實(shí)現(xiàn)可變復(fù)制,還需要遵守NSMutableCopying協(xié)議。

進(jìn)入ViewController.m,導(dǎo)入Person.h,在實(shí)現(xiàn)部分添加以下方法,并在viewDidLoad中調(diào)用。

// 6.自定義類(lèi)的復(fù)制
- (void)copyCustomClass {
    Person *person = [[Person alloc] init];
    [person setName:@"A" withAge:1];
    
    Person *personCopy = [person copy];
    [personCopy setName:@"B" withAge:2];
    // 斷點(diǎn)位置
}

copyCustomClass方法最后一行設(shè)置斷點(diǎn),運(yùn)行demo,可以看到控制臺(tái)輸出如下圖:

CopyBreakpoint.png

通過(guò)上圖可以看到,personpersonCopy內(nèi)存地址不同,且personpersonCopy中的nameage屬性的值各不相同。

3. 修改指針指向

現(xiàn)在看最后一個(gè)示例,在ViewController.m的實(shí)現(xiàn)部分添加以下方法,并在viewDidLoad中調(diào)用該方法。

// 7.更改指針指向地址
- (void)pointToAnotherMemoryAddress {
    // 1.指針a、b同時(shí)指向字符串pro
    NSString *a = @"pro";
    NSString *b = a;
    NSLog(@"Memory location of \n a = %p, \n b = %p", a, b);
    // 斷點(diǎn)1位置
    
    // 2.指針a指向字符串pro648
    a = @"pro648";
    NSLog(@"Memory location of \n a = %p, \n b = %p", a, b);
    // 斷點(diǎn)2位置
}

上述代碼分步說(shuō)明如下:

  1. 指針a指向字符串pro內(nèi)存地址,b = a表示ba的淺復(fù)制,指針b也指向字符串pro內(nèi)存地址。NSLog語(yǔ)句可以說(shuō)明這一問(wèn)題。也可以在注釋斷點(diǎn)1位置所在行設(shè)置斷點(diǎn),查看指針指向的內(nèi)容。
  2. 修改指針a指向字符串pro648,此時(shí)輸出a、b指針?biāo)傅南騼?nèi)存地址。并在斷點(diǎn)2位置所在行設(shè)置斷點(diǎn)。運(yùn)行后可以看到控制臺(tái)輸出如下:
CopyModifyPointer.png

可以看到,a、b指針指向不同內(nèi)存地址,a指向字符串pro648,b指向字符串pro。

這是因?yàn)?/p>

a = @"pro648";

等同于

a = [[NSString alloc] initWithString:@"pro648"];

a = @"pro648"修改了a指針指向的內(nèi)存地址,而b指針依然指向之前的內(nèi)存地址。

NSStringNSMutableString的區(qū)別主要是:NSMutableString對(duì)象所指向內(nèi)存地址中的內(nèi)容可以被修改,而NSString對(duì)象所指向內(nèi)存地址中內(nèi)容不能被修改,但NSString對(duì)象不是常量,可以通過(guò)為NSString對(duì)象重新分配一塊內(nèi)存來(lái)改變其指向的內(nèi)容。

總結(jié)

淺拷貝盡可能少的復(fù)制對(duì)象,集合的淺拷貝副本只是集合結(jié)構(gòu)的副本,而不是集合內(nèi)元素的副本。淺拷貝獲得的副本與原始集合共享各個(gè)元素。

深拷貝復(fù)制一切內(nèi)容。集合的深拷貝會(huì)復(fù)制集合的結(jié)構(gòu)和元素,但如果集合內(nèi)元素也是集合,則涉及到一層深拷貝、完全深拷貝。

Demo名稱(chēng):copy&mutableCopy
源碼地址:https://github.com/pro648/BasicDemos-iOS

參考資料:

  1. Copying Collections
  2. Object copying
  3. Deep Copy and Shallow Copy in Objective C
  4. What is the difference between a deep copy and a shallow copy?

歡迎更多指正:https://github.com/pro648/tips/wiki

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、對(duì)象拷貝有兩種方式:淺復(fù)制和深復(fù)制。顧名思義,淺復(fù)制,并不拷貝對(duì)象本身,僅僅是拷貝指向?qū)ο蟮闹羔?;深?fù)制是直接...
    滴答大閱讀 846評(píng)論 0 2
  • 本文為轉(zhuǎn)載: 作者:zyydeveloper 鏈接:http://www.itdecent.cn/p/5f776a...
    Buddha_like閱讀 1,012評(píng)論 0 2
  • 前言 不敢說(shuō)覆蓋OC中所有copy的知識(shí)點(diǎn),但最起碼是目前最全的最新的一篇關(guān)于 copy的技術(shù)文檔了。后續(xù)發(fā)現(xiàn)有新...
    zyydeveloper閱讀 3,702評(píng)論 4 35
  • 1.什么情況使用關(guān)鍵字copy? 相比 assign 有什么不同? (1).在 ARC 中,在有可能出現(xiàn)循環(huán)引用的...
    愛(ài)賺錢(qián)的依米閱讀 2,108評(píng)論 0 22
  • 概念 在Objective-C中并不是所有的對(duì)象都支持Copy,MutableCopy,遵守NSCopying協(xié)議...
    LeoAu閱讀 9,152評(píng)論 10 28

友情鏈接更多精彩內(nèi)容