總結(jié)copy和mutableCopy相關(guān)

其實(shí)我一直對(duì)于例如屬性中的copy OR [array copy]這樣的使用稀里糊涂的。之前有總結(jié)過,無奈現(xiàn)在又忘了。只能再理一遍了。
首先,我們知道,iOS中,不是所有的對(duì)象都支持copy、mutableCopy。
遵守NSCopying協(xié)議的類可以發(fā)送copy消息,遵守NSMutableCopying協(xié)議的類才可以發(fā)送mutablecopy消息。

顧名思義,copy就是復(fù)制了一個(gè)imutable的對(duì)象,而mutablecopy就是復(fù)制了一個(gè)mutable的對(duì)象。

一、非容器類對(duì)象(像NSString、NSNumber等一類的對(duì)象)

// 1、對(duì)一個(gè)非集合類對(duì)象的copy、mutableCopy
        NSString *string = @"abc";
        NSString *stringCopy = [string copy];
        NSMutableString *stringMutableCopy = [string mutableCopy];

        NSLog(@"%p",string);
        NSLog(@"%p",stringCopy);
        NSLog(@"%p",stringMutableCopy);
對(duì)imutable非集合類對(duì)象的copy、mutableCopy

可以看出:對(duì)一個(gè)iMutable的非集合類對(duì)象string,
調(diào)copy方法,其實(shí)復(fù)制的是string對(duì)象指向那塊內(nèi)存地址的指針,是指針拷貝,string 和stringCopy都是指向的同一塊內(nèi)存地址。
而調(diào)mutableCopy方法,復(fù)制的是string對(duì)象指向的那塊內(nèi)存地址的內(nèi)容,是內(nèi)容拷貝,stringMutableCopy重新指向一塊內(nèi)存地址,而這個(gè)內(nèi)存地址保存的內(nèi)容是從string指向的內(nèi)存地址復(fù)制過來的,stringMutableCopy是一個(gè)可變對(duì)象。

// 2、對(duì)一個(gè)mutable非集合類對(duì)象的copy、mutableCopy
        NSMutableString *mutableString = [NSMutableString stringWithFormat:@"mutableString"];
        NSMutableString *mutableStringCopy = [mutableString copy];
        NSMutableString *mutableStringMutableCopy = [mutableString mutableCopy];
        [mutableStringMutableCopy appendString:@"AAA"];
對(duì)mutable非集合類對(duì)象的copy、mutableCopy

可以看出:對(duì)一個(gè)mutable的非集合類對(duì)象mutableString,
調(diào)copy方法,復(fù)制的是mutableString對(duì)象指向的那塊內(nèi)存地址的內(nèi)容,是內(nèi)容拷貝,但是得到的mutableStringCopy對(duì)象是一個(gè)不可變對(duì)象。
調(diào)mutableCopy方法,是內(nèi)容拷貝,且得到的mutableStringMutableCopy對(duì)象是一個(gè)可變對(duì)象

總結(jié)

在非集合類對(duì)象中:對(duì)immutable對(duì)象進(jìn)行copy操作,是指針復(fù)制,mutableCopy操作時(shí)內(nèi)容復(fù)制;對(duì)mutable對(duì)象進(jìn)行copy和mutableCopy都是內(nèi)容復(fù)制。用代碼簡(jiǎn)單表示如下:

  • [immutableObject copy] // 淺復(fù)制
  • [immutableObject mutableCopy] //深復(fù)制
  • [mutableObject copy] //深復(fù)制
  • [mutableObject mutableCopy] //深復(fù)制
二、集合類對(duì)象的copy與mutableCopy(像NSDictionary、NSArray、NSSet一類的對(duì)象)
// 1、對(duì)一個(gè)imutable Array的copy、mutableCopy        
            NSArray *array = @[@"A",@"B",@"C"];        
            NSArray *arrayCopy = [array copy];        
            NSMutableArray *arrayMutableCopy = [array mutableCopy];    
            [arrayMutableCopy addObject:@"D"];
對(duì)imutable Array的copy、mutableCopy

說明copy操作進(jìn)行了指針拷貝,mutableCopy進(jìn)行了內(nèi)容拷貝。但需要強(qiáng)調(diào)的是:此處的內(nèi)容拷貝,僅僅是拷貝array這個(gè)對(duì)象,array集合內(nèi)部的元素仍然是指針拷貝。

arrayCopy和array是指針復(fù)制,是同一個(gè)NSArray對(duì)象(指向相同的對(duì)象),包括array里面的元素也是指向相同的指針
mutableArrayCopy是兌現(xiàn)復(fù)制,是array的可變副本,指向的對(duì)象和array不同。但是其中的元素和array中的元素指向的是同一個(gè)對(duì)象。mArrayCopy還可以修改自己的對(duì)象。
[mutableArrayCopy addObject:@“de”];
[mutableArrayCopy removeObjectAtIndex:0];//注意,容器內(nèi)的元素內(nèi)容都是指針復(fù)制

再看一個(gè)例子:

NSArray *array = [NSArray arrayWithObject:[NSMutableString stringWithString:@“a”],@“b”,@“c”,nil];

NSArray *arrayCopy = [array copy];
NSMutableArray *arrayMCopy = [array mutableCopy];
//arrayCopy、array指向的是同一個(gè)對(duì)象,arrayMCopy 不一樣。但是其中的元素都是一樣的對(duì)象(同一個(gè)指針)
NSMutableString *testString = [array objectAtIndex:0];

[testString appendString:@“tail”];//這樣以上三個(gè)數(shù)組的首元素都被改變了

對(duì)于容器,其元素對(duì)象始終是指針復(fù)制。如果需要元素對(duì)象也是對(duì)象復(fù)制,就需要實(shí)現(xiàn)深拷貝:

NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"first"],[NSStringstringWithString:@"b"],@"c",nil];    NSArray *deepCopyArray=[[NSArray alloc] initWithArray: array copyItems: YES];
NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject: array]];

trueDeepCopyArray是完全意義上的深拷貝,而deepCopyArray則不是,對(duì)于deepCopyArray內(nèi)的不可變?cè)仄溥€是指針復(fù)制。或者我們自己實(shí)現(xiàn)深拷貝的方法。因?yàn)槿绻萜鞯哪骋辉厥遣豢勺兊?,那你?fù)制完后該對(duì)象仍舊是不能改變的,因此只需要指針復(fù)制即可。除非你對(duì)容器內(nèi)的元素重新賦值,否則指針復(fù)制即已足夠。舉個(gè)例子,[[array objectAtIndex:0]appendstring:@”sd”]后其他的容器內(nèi)對(duì)象并不會(huì)受影響。[[array objectAtIndex:1]和[[deepCopyArray objectAtIndex:0]盡管是指向同一塊內(nèi)存,但是我們沒有辦法對(duì)其進(jìn)行修改——因?yàn)樗遣豢筛淖兊?。所以指針?fù)制已經(jīng)足夠。

// 2、對(duì)一個(gè)mutable Array的copy、mutableCopy        
NSMutableArray *mutableArray = [NSMutableArrayarrayWithObjects:@"A",@"B", nil];        
NSArray *mutableArrayCopy = [mutableArray copy];       
NSMutableArray *mutableArrayMutableCopy = [mutableArray mutableCopy];
[mutableArrayMutableCopy addObject:@"C"];
對(duì)mutable Array的copy、mutableCopy

內(nèi)存地址不一樣,說明對(duì)于mutable Array,調(diào)copy和調(diào)mutable方法都是進(jìn)行內(nèi)容拷貝,array集合內(nèi)部的元素仍然是指針拷貝

三、自定義對(duì)象

當(dāng)然在 ios 中并不是所有的對(duì)象都支持copy,mutableCopy,遵守NSCopying協(xié)議的類可以發(fā)送copy消息,遵守NSMutableCopying協(xié)議的類才可以發(fā)送mutableCopy消息。

假如發(fā)送了一個(gè)沒有遵守上述兩協(xié)議而發(fā)送copy或者 mutableCopy,那么就會(huì)發(fā)生異常。但是默認(rèn)的ios類并沒有遵守這兩個(gè)協(xié)議。如果想自定義一下copy那么就必須遵守NSCopying,并且實(shí)現(xiàn) copyWithZone:方法,如果想自定義一下mutableCopy那么就必須遵守NSMutableCopying,并且實(shí)現(xiàn) mutableCopyWithZone:方法。

如果是我們定義的對(duì)象,那么我們自己要實(shí)現(xiàn)NSCopying,NSMutableCopying這樣就能調(diào)用copy和mutablecopy了。舉個(gè)例子:

@interface MyObj : NSObject<NSCopying,NSMutableCopying>
{
         NSMutableString *name;
         NSString *imutableStr;
         int age;
}
@property (nonatomic, retain) NSMutableString *name;
@property (nonatomic, retain) NSString *imutableStr;
@property (nonatomic) int age;
@end
@implementation MyObj
@synthesize name;
@synthesize age;
@synthesize imutableStr;
- (id)init
{
         if (self = [super init])
         {
                   self.name = [[NSMutableString alloc]init];
                   self.imutableStr = [[NSString alloc]init];
                   age = -1;
         }
         return self;
}
- (void)dealloc
{
         [name release];
         [imutableStr release];
         [super dealloc];
}
- (id)copyWithZone:(NSZone *)zone
{
         MyObj *copy = [[[self class] allocWithZone:zone] init];
         copy->name = [name copy];
         copy->imutableStr = [imutableStr copy];
//       copy->name = [name copyWithZone:zone];;
//       copy->imutableStr = [name copyWithZone:zone];//
         copy->age = age;
         return copy;
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
         MyObj *copy = NSCopyObject(self, 0, zone);
         copy->name = [self.name mutableCopy];
         copy->age = age;
         return copy;

}

四、屬性修飾符相關(guān)

如果property是NSString或NSArray及其子類的時(shí)候,最好選擇使用copy。為什么?
這是為了防止賦值給它的是可變的數(shù)據(jù),如果可變的數(shù)據(jù)發(fā)生了變化,那么該property也會(huì)發(fā)生變化。

@interface Person : NSObject
@property (strong, nonatomic) NSArray *bookArray1;
@property (copy, nonatomic) NSArray *bookArray2;
@end
@implementation Person
//省略setter方法
@end
//Person調(diào)用
main(){    
  NSMutableArray *books = [@[@"book1"] mutableCopy]; 
  Person *person = [[Person alloc] init];    
  person.bookArray1 = books;    
  person.bookArray2 = books;    
  [books addObject:@"book2"]; 
  NSLog(@"bookArray1:%@",person.bookArray1); 
  NSLog(@"bookArray2:%@",person.bookArray2);
}

我們看到,使用strong修飾的person.bookArray1輸出是[book1,book2],而使用copy修飾的person.bookArray2輸出是[book1]。這下可以看出來區(qū)別了吧。

備注:使用strong,則person.bookArray1與可變數(shù)組books指向同一塊內(nèi)存區(qū)域,books內(nèi)容改變,導(dǎo)致person.bookArray1的內(nèi)容改變,因?yàn)閮烧呤峭粋€(gè)東西;而使用copy,person.bookArray2在賦值之前,將books內(nèi)容復(fù)制,創(chuàng)建一個(gè)新的內(nèi)存區(qū)域,所以兩者不是一回事,books的改變不會(huì)導(dǎo)致person.bookArray2的改變。
當(dāng)源字符串是NSString時(shí),由于字符串是不可變的,所以,不管是strong還是copy屬性的對(duì)象,都是指向源對(duì)象,copy操作只是做了次淺拷貝。
當(dāng)源字符串是NSMutableString時(shí),strong屬性只是增加了源字符串的引用計(jì)數(shù),而copy屬性則是對(duì)源字符串做了次深拷貝,產(chǎn)生一個(gè)新的對(duì)象,且copy屬性對(duì)象指向這個(gè)新的對(duì)象。另外需要注意的是,這個(gè)copy屬性對(duì)象的類型始終是NSString,而不是NSMutableString,因此其是不可變的。

這里還有一個(gè)性能問題,即在源字符串是NSMutableString,strong是單純的增加對(duì)象的引用計(jì)數(shù),而copy操作是執(zhí)行了一次深拷貝,所以性能上會(huì)有所差異。而如果源字符串是NSString時(shí),則沒有這個(gè)問題。
所以,在聲明NSString屬性時(shí),到底是選擇strong還是copy,可以根據(jù)實(shí)際情況來定。不過,一般我們將對(duì)象聲明為NSString時(shí),都不希望它改變,所以大多數(shù)情況下,我們建議用copy,以免因可變字符串的修改導(dǎo)致的一些非預(yù)期問題

說到底,其實(shí)就是不同的修飾符,對(duì)應(yīng)不同的setter方法,

  1. strong對(duì)應(yīng)的setter方法,是將_property先release(_property release),然后將參數(shù)retain(property retain),最后是_property = property。
  2. copy對(duì)應(yīng)的setter方法,是將_property先release(_property release),然后拷貝參數(shù)內(nèi)容(property copy),創(chuàng)建一塊新的內(nèi)存地址,最后_property = property。
copy修飾的NSMutableArray屬性(property)初始化問題

對(duì)于屬性:

@property (nonatomic, copy) NSMutableArray *someArray;

若初始化時(shí)使用self.someArray:

self.someArray = [[NSMutableArray alloc] initWithCapacity:200];

當(dāng)使用:

[self.someArray addObject:name];

APP Crash,其中關(guān)鍵 Error Info如下:

-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x7f9c89701c20

原因是,通過copy修飾的property,若通過self.someArray =來賦值初始化,則是通過系統(tǒng)合成setter方法實(shí)現(xiàn),由于設(shè)置copy修飾詞,則返回實(shí)際上是不可變數(shù)組(NSArray),當(dāng)調(diào)用addObject 方法會(huì)報(bào)錯(cuò)。

初始化 或者 賦值 部分,做如下修改:

_someArray = [[NSMutableArray alloc] initWithCapacity:200];

則APP運(yùn)行正常,原因是:
_someArray是實(shí)例變量,實(shí)例變量并沒有 copy 修飾,指向的仍是定義的 NSMutableArray 類型。所以即使后面通過 self.someArray 使用 addObject方法仍然可行,因?yàn)槌跏蓟x值階段獲取的是NSMutableArray類型對(duì)象
最佳解決方案: 其實(shí),就是把copy修飾詞改為strong,因?yàn)榭勺償?shù)組對(duì)象是個(gè)容器,只要其元素中沒有和self對(duì)象存在相互持有造成內(nèi)存泄漏的,則不會(huì)出現(xiàn)任何問題。

深拷貝、淺拷貝:

淺拷貝:
就是對(duì)內(nèi)存地址的復(fù)制,讓目標(biāo)對(duì)象指針和源對(duì)象指向同一片內(nèi)存空間。

淺拷貝只是對(duì)對(duì)象的簡(jiǎn)單拷貝,讓幾個(gè)對(duì)象共用一片內(nèi)存,當(dāng)內(nèi)存銷毀的時(shí)候,指向這片內(nèi)存的幾個(gè)指針需要重新定義才可以使用,要不然會(huì)成為野指針。
iOS里的淺拷貝:iOS里,使用retain關(guān)鍵字進(jìn)行引用計(jì)數(shù),就是一種更加保險(xiǎn)的淺拷貝。他既讓幾個(gè)指針共用同一片內(nèi)存空間,又可以在release由于計(jì)數(shù)的存在,不會(huì)輕易的銷毀內(nèi)存。
深拷貝:
深拷貝是指拷貝對(duì)象的具體內(nèi)容,而內(nèi)存地址是自主分配的,拷貝結(jié)束之后,兩個(gè)對(duì)象雖然存的值是相同的,但是內(nèi)存地址不一樣,兩個(gè)對(duì)象也互不影響,互不干涉。

copy與retain的區(qū)別:
copy是創(chuàng)建一個(gè)新對(duì)象,retain是創(chuàng)建一個(gè)指針,引用對(duì)象計(jì)數(shù)加一。 copy屬性標(biāo)識(shí)兩個(gè)對(duì)象內(nèi)容相同,新的對(duì)象retain count為1, 與舊有對(duì)象引用計(jì)數(shù)無關(guān),舊有對(duì)象沒有變化。copy減少對(duì)象對(duì)上下文的依賴。

深拷貝
iOS提供了copy和mutableCopy方法,顧名思義,copy就是復(fù)制了一個(gè)imutable的對(duì)象,而mutableCopy就是復(fù)制了一個(gè)mutable的對(duì)象。以下將舉幾個(gè)例子來說明。這里指的是NSString, NSNumber等等一類的對(duì)象。

NSString *string = @”dddd";
NSString *stringCopy = [string copy];
NSMutableString *stringDCopy = [string mutableCopy];
[stringMCopy appendString:@"!!"];

查看內(nèi)存可以發(fā)現(xiàn),string和stringCopy指向的是同一塊內(nèi)存區(qū)域(weak reference),引用計(jì)數(shù)沒有發(fā)生改變。而stringMCopy則是我們所說的真正意義上的復(fù)制,系統(tǒng)為其分配了新內(nèi)存,是兩個(gè)獨(dú)立的字符串內(nèi)容是一樣的。

說個(gè)題外話

如何理解NSString是不可變的,一旦創(chuàng)建就不能修改他。
NSString * string=@"aaaa";string=@“bbbb”;

意思是 這個(gè)地址的內(nèi)容是不能變了,只能是aaaa
然后賦值是把string指向的地址變了,所以內(nèi)容變了,地址不變內(nèi)容是不能變地
NSMutableString 地址不變的情況下內(nèi)容可以變的

最后編輯于
?著作權(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)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 為什么很多內(nèi)置類如UITableView的delegate屬性都是assign而不是retain? 所有的引用計(jì)數(shù)...
    煙雨平生花飛舞閱讀 1,282評(píng)論 0 3
  • 前言 不敢說覆蓋OC中所有copy的知識(shí)點(diǎn),但最起碼是目前最全的最新的一篇關(guān)于 copy的技術(shù)文檔了。后續(xù)發(fā)現(xiàn)有新...
    zyydeveloper閱讀 3,746評(píng)論 4 35
  • 本文為轉(zhuǎn)載: 作者:zyydeveloper 鏈接:http://m.itdecent.cn/p/5f776a...
    Buddha_like閱讀 1,030評(píng)論 0 2
  • 我從不是一個(gè)意志堅(jiān)定的人,很容易受環(huán)境影響。初三時(shí),晚自習(xí)后回到家,幾乎每日都會(huì)一個(gè)人對(duì)著墻壁發(fā)呆,時(shí)常有天馬行空...
    冷雨淋門閱讀 217評(píng)論 0 0
  • 紙已鋪好,墨已蘸飽。一言鋪開,只說四點(diǎn)。 1為什么要吃飯? 答:為了補(bǔ)充能量,提供各種所需營(yíng)養(yǎng)。 2為什么必須是3...
    草上的微光閱讀 427評(píng)論 8 9

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