淺析iOS的淺復(fù)制與深復(fù)制

原文:淺析iOS的淺復(fù)制與深復(fù)制

最近同事問我一個問題:原數(shù)組A缕贡,進行復(fù)制得到數(shù)組B翁授,改變數(shù)組B的Person元素對象,不影響數(shù)組A的Person元素對象晾咪,如何操作收擦?
第一感覺是進行深復(fù)制,同樣數(shù)組里面的元素對象也要進行深復(fù)制谍倦,于是就找到相關(guān)的API:

- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag;

然后同事跟我說還有其他方法嗎塞赂?要不分享一下iOS的復(fù)制吧?然后就有了這篇文章昼蛀。文章如有錯誤歡迎指出更正宴猾,小弟虛心受教,也怕誤人子弟叼旋。

為什么要復(fù)制仇哆?

定義:在面向?qū)ο缶幊讨校瑢ο髲?fù)制是創(chuàng)建一個現(xiàn)有對象的副本夫植,即面向?qū)ο缶幊讨械囊粋€數(shù)據(jù)單元讹剔。生成的對象稱為對象副本或者僅僅是原始對象的副本油讯。

意義:為了操作對象副本數(shù)據(jù)時不影響原對象數(shù)據(jù)

在iOS中哪些類支持復(fù)制功能?

NSString延欠、NSMutableString陌兑、NSArray、NSMutableArray由捎、NSDictionary兔综、NSMutableDictionary…
不難發(fā)現(xiàn)在API中這些類需要遵循<NSCopying, NSMutableCopying>。至于為什么要遵循這兩個協(xié)議狞玛,協(xié)議中需要實現(xiàn)哪些方法后面涉及软驰,這里不做闡釋。但是至少可以總結(jié)出想要類支持復(fù)制功能为居,就要遵循<NSCopying, NSMutableCopying>以及實現(xiàn)對應(yīng)方法碌宴。

淺復(fù)制 or 深復(fù)制杀狡?

復(fù)制主要分為淺復(fù)制和深復(fù)制蒙畴。

  • 淺復(fù)制:拷貝指向?qū)ο蟮闹羔槪皇菍ο蟊旧怼?/li>
  • 深復(fù)制:拷貝對象內(nèi)容指向另外一塊內(nèi)存呜象。

下圖是淺復(fù)制與深復(fù)制的關(guān)系(下圖來自官方文檔)


image

舉個例子:

    NSString *immutableStr = @"不可變字符串";
    NSString *immutableStrCopy = [immutableStr copy];
    NSString *immutableStrMutableCopy = [immutableStr mutableCopy];
    NSLog(@"%@--%p", immutableStr, immutableStr);                       // name--0x100001040
    NSLog(@"%@--%p", immutableStrCopy, immutableStrCopy);               // name--0x100001040
    NSLog(@"%@--%p", immutableStrMutableCopy, immutableStrMutableCopy); // name--0x10075b320
    
    NSLog(@"------------------------");
    
    NSMutableString *mutableStr = [[NSMutableString alloc] initWithString:@"可變字符串"];
    NSMutableString *mutableStrCopy = [mutableStr copy];
    NSMutableString *mutableStrMutableCopy = [mutableStr mutableCopy];
    NSLog(@"%@--%p", mutableStr, mutableStr);                       // string--0x604000249a50
    NSLog(@"%@--%p", mutableStrCopy, mutableStrCopy);               // string--0xa00676e697274736
    NSLog(@"%@--%p", mutableStrMutableCopy, mutableStrMutableCopy); // string--0x604000249720

    NSLog(@"------------------------");
    
    NSArray *immutableArray = @[@"1", @"2"];
    NSArray *immutableArrayCopy = [immutableArray copy];
    NSArray *immutableArrayMutableCopy = [immutableArray mutableCopy];
    NSLog(@"%@--%p", immutableArray, immutableArray);                       // 1,2--0x60000003a200
    NSLog(@"%@--%p", immutableArrayCopy, immutableArrayCopy);               // 1,2--0x60000003a200
    NSLog(@"%@--%p", immutableArrayMutableCopy, immutableArrayMutableCopy); // 1,2--0x600000449de0
    
    NSLog(@"------------------------");
    
    NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithArray:@[@"1", @"2"]];
    NSMutableArray *mutableArrayCopy = [mutableArray copy];
    NSMutableArray *mutableArrayMutableCopy = [mutableArray mutableCopy];
    NSLog(@"%@--%p", mutableArray, mutableArray);                       // 1,2--0x60000005c9e0
    NSLog(@"%@--%p", mutableArrayCopy, mutableArrayCopy);               // 1,2--0x60000003a340
    NSLog(@"%@--%p", mutableArrayMutableCopy, mutableArrayMutableCopy); // 1,2--0x60000005ca40

總結(jié):

  • 不可變對象:進行copy得到的是淺復(fù)制膳凝,進行mutableCopy得到的是深復(fù)制。
  • 可變對象:無論進行copy還是mutableCopy都是深復(fù)制恭陡。
類型 copy mutableCopy
NSString 淺復(fù)制 深復(fù)制
NSMutableString 深復(fù)制 深復(fù)制
NSArray 淺復(fù)制 深復(fù)制
NSMutableArray 深復(fù)制 深復(fù)制
... ... ...

聲明類型一定是進行復(fù)制后的類型嗎蹬音?

[圖片上傳失敗...(image-7fdc29-1530757237487)]

斷點調(diào)試發(fā)現(xiàn)不是,比如:immutableStrMutableCopy聲明的是不可變類型NSString休玩,NSString類型是不可以對字符串進行增刪操作的著淆,然而NSMutableString類型卻可以。因為iOS是動態(tài)語言拴疤,運行時才斷定是什么類型永部,顯然immutableStrMutableCopy實際是NSMutableString,可以對它進行追加字符串呐矾,例如:

[(NSMutableString *)immutableStrMutableCopy appendString:@"追加字符"];
NSLog(@"%@--%p", immutableStrMutableCopy, immutableStrMutableCopy); // string追加字符--0x10075b320

相反mutableStrCopy聲明的是NSMutableString類型苔埋,實際是NSString類型,如果對mutableStrCopy進行增刪操作蜒犯,必然crash组橄。

[mutableStrCopy appendString:@"will crash"];

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSTaggedPointerString appendString:]: unrecognized selector sent to instance 0xa00676e697274736'
mutableStrCopy對象沒有appendString:這個方法。

以此類推罚随,同樣對于數(shù)組來說:

[圖片上傳失敗...(image-2dc523-1530757237487)]

顯然根據(jù)斷點信息:
immutableArrayMutableCopy是NSMutableArray玉工,可以添加新對象

[(NSMutableArray *)immutableArrayMutableCopy addObject:@"3"];
NSLog(@"%@--%p", immutableArrayMutableCopy, immutableArrayMutableCopy); // 1,2,3--0x600000449de0

mutableArrayCopy是NSArray,添加新的對象會crash

// crash
[mutableArrayCopy addObject:@"3"];

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x600000430600'

總結(jié):

  • 不可變對象進行mutableCopy得到的是可變對象.
  • 可變對象進行copy得到的是不可變對象淘菩。

深復(fù)制真的是深復(fù)制嗎遵班?

    NSArray *immutableArray = @[@"1", @"2"];
    NSArray *immutableArrayCopy = [immutableArray copy];
    NSArray *immutableArrayMutableCopy = [immutableArray mutableCopy];
    NSLog(@"%@--%p", immutableArray, immutableArray);                       // 1,2--0x60000003a200
    NSLog(@"%@--%p", immutableArrayCopy, immutableArrayCopy);               // 1,2--0x60000003a200
    NSLog(@"%@--%p", immutableArrayMutableCopy, immutableArrayMutableCopy); // 1,2--0x600000449de0
    
    NSLog(@"------------------------");
    
    NSLog(@"%@--%p", [immutableArray firstObject], [immutableArray firstObject]);      // 1--0x10d448078
    NSLog(@"%@--%p", [immutableArrayCopy firstObject], [immutableArrayCopy firstObject]);  // 1--0x10d448078
    NSLog(@"%@--%p", [immutableArrayMutableCopy firstObject], [immutableArrayMutableCopy firstObject]); // 1--0x10d448078
    NSLog(@"------------------------");
    
    NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithArray:@[@"1", @"2"]];
    NSMutableArray *mutableArrayCopy = [mutableArray copy];
    NSMutableArray *mutableArrayMutableCopy = [mutableArray mutableCopy];
    NSLog(@"%@--%p", mutableArray, mutableArray);                       // 1,2--0x60000005c9e0
    NSLog(@"%@--%p", mutableArrayCopy, mutableArrayCopy);               // 1,2--0x60000003a340
    NSLog(@"%@--%p", mutableArrayMutableCopy, mutableArrayMutableCopy); // 1,2--0x60000005ca40
    
    NSLog(@"------------------------");
    
    NSLog(@"%@--%p", [mutableArray firstObject], [mutableArray firstObject]);                       // 1--0x10f1f3078
    NSLog(@"%@--%p", [mutableArrayCopy firstObject], [mutableArrayCopy firstObject]);               // 1--0x10f1f3078
    NSLog(@"%@--%p", [mutableArrayMutableCopy firstObject], [mutableArrayMutableCopy firstObject]); // 1--0x10f1f3078

從上面的代碼篩選出深復(fù)制的例子:

    NSArray *immutableArray = @[@"1", @"2"];
    NSArray *immutableArrayMutableCopy = [immutableArray mutableCopy];
    NSLog(@"%@--%p", immutableArray, immutableArray);                       // 1,2--0x60000003a200
    NSLog(@"%@--%p", immutableArrayMutableCopy, immutableArrayMutableCopy); // 1,2--0x600000449de0
    
    NSLog(@"------------------------");
    
    NSLog(@"%@--%p", [immutableArray firstObject], [immutableArray firstObject]);      // 1--0x10d448078
    NSLog(@"%@--%p", [immutableArrayMutableCopy firstObject], [immutableArrayMutableCopy firstObject]); // 1--0x10d448078
    NSLog(@"------------------------");
    
    NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithArray:@[@"1", @"2"]];
    NSMutableArray *mutableArrayMutableCopy = [mutableArray mutableCopy];
    NSLog(@"%@--%p", mutableArray, mutableArray);                       // 1,2--0x60000005c9e0
    NSLog(@"%@--%p", mutableArrayMutableCopy, mutableArrayMutableCopy); // 1,2--0x60000005ca40
    
    NSLog(@"------------------------");
    
    NSLog(@"%@--%p", [mutableArray firstObject], [mutableArray firstObject]);                       // 1--0x10f1f3078
    NSLog(@"%@--%p", [mutableArrayMutableCopy firstObject], [mutableArrayMutableCopy firstObject]); // 1--0x10f1f3078

發(fā)現(xiàn)深復(fù)制只作用于數(shù)組對象這層,而數(shù)組對象里面存放的元素并沒有復(fù)制。引用官方文檔里面的一句話:

“This kind of copy is only capable of producing a one-level-deep copy. If you only need a one-level-deep copy...
If you need a true deep copy, such as when you have an array of arrays…”
理解為這只是單層深復(fù)制(one-level-deep copy)

那么現(xiàn)在區(qū)分一下概念:

  • 淺復(fù)制(shallow copy):在淺復(fù)制操作時费奸,對于被復(fù)制對象指針復(fù)制弥激。
  • 深復(fù)制(one-level-deep copy):在深復(fù)制操作時,對于被復(fù)制對象愿阐,至少有一層是深復(fù)制微服。
  • 完全復(fù)制(real-deep copy):在完全復(fù)制操作時,對于被復(fù)制對象的每一層都是對象復(fù)制缨历。
image

根據(jù)上圖(來自官網(wǎng))舉例例子場景說明:

  • 單層深復(fù)制:數(shù)組A以蕴,進行深復(fù)制得到數(shù)組B,當(dāng)修改數(shù)組B里面的對象時辛孵,數(shù)組A里面的對象也會跟著變丛肮。
  • 完全復(fù)制:數(shù)組A,進行深復(fù)制得到數(shù)組B魄缚,當(dāng)修改數(shù)組B里面的對象時宝与,數(shù)組A里面的對象不會跟著變。

那么對于集合怎樣才算是完全復(fù)制呢冶匹?

歸檔方式

    NSArray *immutableArray = @[@"1", @"2"];
    NSArray *immutableArrayMutableCopy = [immutableArray mutableCopy];
    // 歸檔深復(fù)制
    NSArray *archiverDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:immutableArray]];
    
    NSLog(@"%@--%p", immutableArray, immutableArray);                       // 1,2--0x604000430900
    NSLog(@"%@--%p", immutableArrayMutableCopy, immutableArrayMutableCopy); // 1,2--0x604000255300
    NSLog(@"%@--%p", archiverDeepCopyArray, archiverDeepCopyArray); // 1,2--0x604000430840
    
    NSLog(@"------------------------");
    
    NSLog(@"%@--%p", [immutableArray firstObject], [immutableArray firstObject]);      // 1--0x10d448078
    NSLog(@"%@--%p", [immutableArrayMutableCopy firstObject], [immutableArrayMutableCopy firstObject]); // 1--0x10d448078
    NSLog(@"%@--%p", [archiverDeepCopyArray firstObject], [archiverDeepCopyArray firstObject]); // 1--0xa000000000000311
    NSLog(@"------------------------");
    
    NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithArray:@[@"1", @"2"]];
    NSMutableArray *mutableArrayMutableCopy = [mutableArray mutableCopy];
    // 歸檔深復(fù)制
    NSArray *archiverDeepCopyArray1 = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:mutableArray]];
    
    
    NSLog(@"%@--%p", mutableArray, mutableArray);                       // 1,2--0x60000005c9e0
    NSLog(@"%@--%p", mutableArrayMutableCopy, mutableArrayMutableCopy); // 1,2--0x60000005ca40
    NSLog(@"%@--%p", archiverDeepCopyArray1, archiverDeepCopyArray1); // 1,2--0x600000253a10
    
    NSLog(@"------------------------");
    
    NSLog(@"%@--%p", [mutableArray firstObject], [mutableArray firstObject]);                       // 1--0x10f1f3078
    NSLog(@"%@--%p", [mutableArrayMutableCopy firstObject], [mutableArrayMutableCopy firstObject]); // 1--0x10f1f3078
    NSLog(@"%@--%p", [archiverDeepCopyArray1 firstObject], [archiverDeepCopyArray1 firstObject]); // 1--0xa000000000000311

通過上述例子使用歸檔方式可以達到完全復(fù)制习劫。

自帶API初始化方式

    NSArray *immutableArray = @[@"1", @"2"];
    NSArray *immutableArrayMutableCopy = [immutableArray mutableCopy];
    // 深復(fù)制
    // copyItems參數(shù)表示:是否里面的元素也進行復(fù)制, NO表示淺復(fù)制嚼隘, YES表示深復(fù)制
    NSArray *copyItemsDeepCopyArray = [[NSArray alloc] initWithArray:immutableArray copyItems:YES];
    NSArray *copyItemsDeepCopyArray2 = [[NSMutableArray alloc] initWithArray:immutableArray copyItems:YES];
    
    NSLog(@"%@--%p", immutableArray, immutableArray);                       // 1,2--0x604000430900
    NSLog(@"%@--%p", immutableArrayMutableCopy, immutableArrayMutableCopy); // 1,2--0x604000255300
    NSLog(@"%@--%p", copyItemsDeepCopyArray, copyItemsDeepCopyArray); // 1,2--0x604000430840
    
    NSLog(@"------------------------");
    
    NSLog(@"%@--%p", [immutableArray firstObject], [immutableArray firstObject]);      // 1--0x10d448078
    NSLog(@"%@--%p", [immutableArrayMutableCopy firstObject], [immutableArrayMutableCopy firstObject]); // 1--0x10d448078
    NSLog(@"%@--%p", [copyItemsDeepCopyArray firstObject], [copyItemsDeepCopyArray firstObject]); // 1--0xa000000000000311
    
    NSLog(@"------------------------");
    
    NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithArray:@[@"1", @"2"]];
    NSMutableArray *mutableArrayMutableCopy = [mutableArray mutableCopy];
    // 深復(fù)制
    NSArray *copyItemsDeepCopyArray1 = [[NSMutableArray alloc] initWithArray:mutableArray copyItems:YES];
    
    NSLog(@"%@--%p", mutableArray, mutableArray);                       // 1,2--0x60000005c9e0
    NSLog(@"%@--%p", mutableArrayMutableCopy, mutableArrayMutableCopy); // 1,2--0x60000005ca40
    NSLog(@"%@--%p", copyItemsDeepCopyArray1, copyItemsDeepCopyArray1); // 1,2--0x60000025c890
    
    NSLog(@"------------------------");
    
    NSLog(@"%@--%p", [mutableArray firstObject], [mutableArray firstObject]);                       // 1--0x10f1f3078
    NSLog(@"%@--%p", [mutableArrayMutableCopy firstObject], [mutableArrayMutableCopy firstObject]); // 1--0x10f1f3078
    NSLog(@"%@--%p", [copyItemsDeepCopyArray1 firstObject], [copyItemsDeepCopyArray1 firstObject]); // 1--0xa000000000000311

使用上述兩種方式均可達到完全復(fù)制的效果诽里。

有四個注意點:

  1. 使用歸檔方式:歸檔的對象必須遵循NSCoding協(xié)議并實現(xiàn)協(xié)議方法。
  2. 使用歸檔方式:使用NSKeyedArchiver歸檔的對象是什么類型飞蛹,那么NSKeyedUnarchiver解檔出來的對象就是什么類型
    比如歸檔的是NSArray類型谤狡,解檔得到的類型也是NSArray:
    NSArray *archiverDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:immutableArray]];
  3. 使用實例化方法:如果copyItems為YES,那么數(shù)組的元素對象必須遵循NSCopying協(xié)議并實現(xiàn)協(xié)議方法卧檐,否則crash
  4. 使用實例化方法:使用什么類型進行初始化的墓懂,得到的就是什么類型的對象。
    比如使用NSMutableArray進行初始化泄隔,那么copyItemsDeepCopyArray1就是NSMutableArray類型塘砸,而不是NSArray類型:
    NSArray *copyItemsDeepCopyArray1 = [[NSMutableArray alloc] initWithArray:mutableArray copyItems:YES];

如何讓對象支持復(fù)制操作照捡?

上面提及到只要遵循<NSCopying, NSMutableCopying>以及實現(xiàn)協(xié)議方法就可以實現(xiàn)復(fù)制操作,那先看看這兩個協(xié)議有什么協(xié)議方法:

@protocol NSCopying

- (id)copyWithZone:(nullable NSZone *)zone;

@end

@protocol NSMutableCopying

- (id)mutableCopyWithZone:(nullable NSZone *)zone;

@end

自定義Person類實現(xiàn)淺復(fù)制&深復(fù)制

// Person.h
@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

@end


// Person.m
// 深復(fù)制
- (id)copyWithZone:(NSZone *)zone{
    //創(chuàng)建新的對象空間
    Person *p = [[self class] allocWithZone:zone];
    p.name = self.name;
    p.age = self.age;
    return p;
}

// 淺復(fù)制(偽復(fù)制)
//- (id)copyWithZone:(NSZone *)zone{
//    // 返回對象本身
//    return self;
//}

// 深復(fù)制
- (id)mutableCopyWithZone:(NSZone *)zone{
    //創(chuàng)建新的對象空間
    Person *p = [[self class] allocWithZone:zone];
    
    //為每個屬性創(chuàng)建新的空間,并將內(nèi)容復(fù)制
    p.name = self.name;
    p.age = self.age;
    return p;
}

測試:

    Person *person = [[Person alloc] init];
    person.name = @"daisuke";
    person.age = 26;
    
    Person *personCopy = [person copy];
    Person *personMutableCopy = [person mutableCopy];
    
    NSLog(@"%@--%p", person, person.name);                       // <Person: 0x6040000275e0>--0x100349088
    NSLog(@"%@--%p", personCopy, personCopy.name);               // <Person: 0x604000222340>--0x100349088
    NSLog(@"%@--%p", personMutableCopy, personMutableCopy.name); // <Person: 0x604000222b80>--0x100349088

從打印結(jié)果的出都實現(xiàn)了深復(fù)制操作阶界。但是一般來說自定義對象不需要實現(xiàn)NSMutableCopying協(xié)議侥钳,因為對象不像容器旺入,本身沒有相關(guān)存儲擴展等功能场躯。

看到這里可能細(xì)心的人發(fā)現(xiàn)都是深復(fù)制,為什么person對象里面的name屬性地址沒有深復(fù)制湾揽?

由于NSString特殊性瓤逼,系統(tǒng)會判斷字符串屬性在同一內(nèi)容前提下笼吟,使用@“”或者initWith..方法創(chuàng)建的對象作為常量,放在常量區(qū)則不會開辟新的內(nèi)存空間霸旗。

驗證:

    NSString *text = @"123";
    NSLog(@"%p---", text); // 0x10f36b0c8
    NSString *text1 = @"123";
    NSLog(@"%p---", text1); // 0x10f36b0c8
    NSString *text2 = [[NSString alloc] initWithString:@"123"];
    NSLog(@"%p---", text2); // 0x10f36b0c8

發(fā)現(xiàn)不同變量贷帮,因為內(nèi)容一致,指向的地址是一樣的诱告。所以除了改變值撵枢,難道就沒有開辟新內(nèi)存空間的方法了嗎?有

NSString *text3 = [NSString stringWithFormat:@"123"];
NSLog(@"%p---", text3); // 0xa000000003332313
NSString *text4 = [[NSString alloc] initWithFormat:@"123"];
NSLog(@"%p---", text4); // 0xa000000003332313

會發(fā)現(xiàn)同樣的內(nèi)容精居,但是地址是不一樣的锄禽,而且長度也不一樣。

總結(jié):

  • @“”或者initWith..方法的變量存放在常量區(qū)靴姿,由系統(tǒng)管理內(nèi)存
  • Format:方式創(chuàng)建的變量存放在堆區(qū)

所以想要實現(xiàn)完全的復(fù)制可以這樣做:

// Person.m
- (id)copyWithZone:(NSZone *)zone{
    //創(chuàng)建新的對象空間
    Person *p = [[self class] allocWithZone:zone];
    p.name = [NSString stringWithFormat:@"%@", self.name];
    p.age = self.age;
    return p;
}

實現(xiàn)完全復(fù)制無非就是怕修改副本對象的屬性沃但,從而影響到原對象的屬性。對于字符串來說佛吓,其實你可以不需要這樣做宵晚。因為副本對象修改字符串屬性不會影響原對象的字符串屬性。

驗證:


    Person *person = [[Person alloc] init];
    person.name = @"daisuke";
    person.age = 26;
    
    Person *personCopy = [person copy];
    Person *personMutableCopy = [person mutableCopy];
    
    NSLog(@"%@--%p", person, person.name);                       // <Person: 0x6040000275e0>--0x100349088
    NSLog(@"%@--%p", personCopy, personCopy.name);               // <Person: 0x604000222340>--0x100349088
    NSLog(@"%@--%p", personMutableCopy, personMutableCopy.name); // <Person: 0x604000222b80>--0x100349088
    
    personCopy.name = @"修改name的值";
    NSLog(@"%@-%@-%p", person, person.name, person.name);               // <Person: 0x60000003f140>-daisuke-0x103f1e088
    NSLog(@"%@-%@-%p", personCopy, personCopy.name, personCopy.name);   // <Person: 0x6000002302a0>-修改name的值-0x103f1e0c8

上面的代碼中看到盡管兩個對象name的地址是一樣的辈毯,但是修改對象personCopy的屬性name的值坝疼,并沒有影響到對象person的name值搜贤。

對象中有對象屬性時如何支持復(fù)制操作谆沃?

新建一個Contact類作為person的一個屬性:

// Person.h
@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
/// 聯(lián)系方式
@property (nonatomic, strong) Contact *contact;

@end

在copyWithZone:方法中

// Person.m
- (id)copyWithZone:(NSZone *)zone{
    //創(chuàng)建新的對象空間
    Person *p = [[self class] allocWithZone:zone];
    p.name = self.name;
    p.age = self.age;
    p.contact = self.contact;
    return p;
}

測試:

    Person *person = [[Person alloc] init];
    person.name = @"daisuke";
    person.age = 26;
    
    Contact *contact = [[Contact alloc] init];
    contact.phone = @"12345678900";
    contact.email = @"feng@gmail.com";
    person.contact = contact;

    Person *personCopy = [person copy];
    NSLog(@"%@--%p--%@--%p", person, person.name, person.contact.phone, person.contact);
    NSLog(@"%@--%p--%@--%p", personCopy, personCopy.name,personCopy.contact.phone, personCopy.contact);
    // <Person: 0x600000024a00>--0x10
b2a8088--12345678900--0x60000022fdc0
    // <Person: 0x600000230300>--0x10b2a8088--12345678900--0x60000022fdc0

    personCopy.contact.phone = @"999999999";
    personCopy.name = @"這是新的名稱";
    NSLog(@"%@--%p--%@--%p", person, person.name, person.contact.phone, person.contact);
    NSLog(@"%@--%p--%@--%p", personCopy, personCopy.name,personCopy.contact.phone, personCopy.contact);
    // <Person: 0x600000024a00>--0x10b2a8088--999999999--0x60000022fdc0
    // <Person: 0x600000230300>--0x10b2a8128--999999999--0x60000022fdc0

從打印信息看到person是進行了深復(fù)制,但是對象contact指向的是同一個指針仪芒,顯然并沒有進行深復(fù)制(單層深復(fù)制)唁影,也可以從copyWithZone:方法中看到p.contact = self.contact;只是簡單的指針賦值。如果改變副本personCopy的contact對象屬性掂名,原對象的contact也會跟著改變据沈,這不是我們想要的結(jié)果。那應(yīng)該怎么辦呢饺蔑?方法是讓屬性對象也實現(xiàn)深復(fù)制功能

例如Contact也實現(xiàn)深復(fù)制:

// Contact.m
-(id)copyWithZone:(NSZone *)zone{
    Contact *contact = [[self class] allocWithZone:zone];
    contact.phone = self.phone;
    contact.email = self.email;
    return contact;
}

Contact類繼承NSCopying協(xié)議實現(xiàn)了深復(fù)制功能锌介,然后在Person類的深復(fù)制方法中修改為:

// Person.m
- (id)copyWithZone:(NSZone *)zone{
    //創(chuàng)建新的對象空間
    Person *p = [[self class] allocWithZone:zone];
    p.name = self.name;
    p.age = self.age;
//    p.contact = self.contact;
    // 深復(fù)制
    p.contact = [self.contact copy];
    return p;
}

這樣就能讓對象屬性實現(xiàn)深復(fù)制效果,驗證:

    Person *person = [[Person alloc] init];
    person.name = @"daisuke";
    person.age = 26;
    
    Contact *contact = [[Contact alloc] init];
    contact.phone = @"12345678900";
    contact.email = @"feng@gmail.com";
    person.contact = contact;

    Person *personCopy = [person copy];
    NSLog(@"%@--%p--%@--%p", person, person.name, person.contact.phone, person.contact);
    NSLog(@"%@--%p--%@--%p", personCopy, personCopy.name,personCopy.contact.phone, personCopy.contact);
    // <Person: 0x60400042f820>--0x102d04088--12345678900--0x60400042f7c0
    // <Person: 0x60400042f860>--0x102d04088--12345678900--0x60400042f8a0
    
    personCopy.contact.phone = @"999999999";
    personCopy.name = @"這是新的名稱";
    NSLog(@"%@--%p--%@--%p", person, person.name, person.contact.phone, person.contact);
    NSLog(@"%@--%p--%@--%p", personCopy, personCopy.name,personCopy.contact.phone, personCopy.contact);
    // <Person: 0x60400042f820>--0x102d04088--12345678900--0x60400042f7c0
    // <Person: 0x60400042f860>--0x102d04128--999999999--0x60400042f8a0

從打印信息可以看到,person與personCopy指針不一樣猾警,person的contact對象屬性與personCopy的contact對象屬性指針不一樣孔祸。
而且當(dāng)修改personCopy的contact對象屬性中的phone值時,原對象person的contact對象屬性中的phone值沒有跟著改變发皿,所以是實現(xiàn)了完全復(fù)制崔慧。

如何讓子類對象支持復(fù)制操作?

為了不影響Person類穴墅,新建一個Father類:

// Father.h
@interface Father : NSObject<NSCopying>

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
/// 聯(lián)系方式
@property (nonatomic, strong) Contact *contact;

@end

// Father.m
@implementation Father

- (id)copyWithZone:(NSZone *)zone{
    //創(chuàng)建新的對象空間
    Father *f = [[self class] allocWithZone:zone];
    f.name = self.name;
    f.age = self.age;
    // 深復(fù)制
    f.contact = [self.contact copy];
    return f;
}

@end

Father類稍微做了調(diào)整惶室,把繼承NSCopying協(xié)議放在.h文件中,再新建一個Son類繼承Father類:

// Son.h
@interface Son : Father

@property (nonatomic, assign) double height;
@property (nonatomic, assign) double weight;
/// 成績
@property (nonatomic, strong) Score *score;
@end

因為是繼承關(guān)系温自,子類也遵循了NSCopying協(xié)議,但是并沒有重寫copyWithZone:方法皇钞,如果進行復(fù)制操作會是怎樣的結(jié)果呢悼泌?

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Son *son = [[Son alloc] init];
    son.height = 173.0;
    son.weight = 120;
    
    Score *score = [[Score alloc] init];
    score.math = 100.0;
    score.chinese = 99.0;
    score.english = 88;
    son.score = score;
    
    Son *sonCopy = [son copy];
    
    NSLog(@"%@--%@", son, son.score);                       // <Son: 0x604000266c00>--<Score: 0x60400003e5a0>
    NSLog(@"%@--%@", sonCopy, sonCopy.score);               // <Son: 0x604000266d80>--(null)
    
}

打印信息顯示,son對象進行了深復(fù)制夹界,但是sonCopy的score對象是空的券躁。為什么呢?因為進行[son copy]操作時掉盅,子類沒有重寫copyWithZone:方法也拜,會去父類那里找,也可以從斷點中看到:

[圖片上傳失敗...(image-a31a9f-1530757237487)]

父類中self指向的是Son類趾痘,那么[[self class] allocWithZone:zone]表示給son創(chuàng)建一個新的空間慢哈,也就是之前打印信息顯示結(jié)果一樣完成了深復(fù)制操作,但是f對象中沒有給score對象屬性賦值永票,所以是null的卵贱。

想要score有值,必然要重寫copyWithZone:方法侣集。

// Son.m
@implementation Son

- (id)copyWithZone:(NSZone *)zone{
    Son *s = [[self class] allocWithZone:zone];
    s.height = self.height;
    s.weight = self.height;
    s.score = [self.score copy];
    return s;
}

@end

而且Score也要實現(xiàn)深復(fù)制操作:

// Score.m
@implementation Score

- (id)copyWithZone:(NSZone *)zone{
    Score *s = [[self class] allocWithZone:zone];
    s.math = self.math;
    s.chinese = self.chinese;
    s.english = self.english;
    return s;
}
@end

接下來驗證一下:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Son *son = [[Son alloc] init];
    son.height = 173.0;
    son.weight = 120;
    
    Score *score = [[Score alloc] init];
    score.math = 100.0;
    score.chinese = 99.0;
    score.english = 88;
    son.score = score;
    
    Son *sonCopy = [son copy];
    
    NSLog(@"%@--%@", son, son.score);                       // <Son: 0x6040002760c0>--<Score: 0x604000429e20>
    NSLog(@"%@--%@", sonCopy, sonCopy.score);      // <Son: 0x604000276180>--<Score: 0x604000429e00>
    
}

通過打印信息看到實現(xiàn)深復(fù)制功能键俱。

但是可能有一個疑問?Son是繼承Father類的世分,要是Father類的屬性也有值呢编振?
首先不修改復(fù)制方法的代碼,簡單的給Father類的屬性賦值測試一下先:

- (void)viewDidLoad {
    [super viewDidLoad];

    Son *son = [[Son alloc] init];
    son.height = 173.0;
    son.weight = 120;
    
    Score *score = [[Score alloc] init];
    score.math = 100.0;
    score.chinese = 99.0;
    score.english = 88;
    son.score = score;
    
    Contact *contact = [[Contact alloc] init];
    contact.phone = @"123456789";
    contact.email = @"feng@gmail";
    son.contact = contact;
    son.name = @"daisuke";
    
    Son *sonCopy = [son copy];
    
    NSLog(@"%@--%@--%@--%@", son, son.score, son.name, son.contact);                       
// <Son: 0x604000273240>--<Score: 0x6040000335e0>--daisuke--<Contact: 0x604000033600>
    NSLog(@"%@--%@--%@--%@", sonCopy, sonCopy.score, sonCopy.name, sonCopy.contact);               
// <Son: 0x604000273580>--<Score: 0x604000236c00>--(null)--(null)
    
}

從打印信息看到sonCopy.name和sonCopy.contact兩個值都是null的臭埋,為什么呢踪央?
因為Son類重寫了copyWithZone:方法,自己完成了深復(fù)制操作瓢阴,并沒有考慮到父類也需要深復(fù)制畅蹂。自然而然沒有運行父類的copyWithZone:方法,所以就出現(xiàn)了sonCopy對象的父類屬性值時null的荣恐。
修改如下:

// Son.m
- (id)copyWithZone:(NSZone *)zone{
    // 使用super
    Son *s = [super copyWithZone:zone];
    s.height = self.height;
    s.weight = self.height;
    s.score = [self.score copy];
    return s;
}

測試:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Son *son = [[Son alloc] init];
    son.height = 173.0;
    son.weight = 120;
    
    Score *score = [[Score alloc] init];
    score.math = 100.0;
    score.chinese = 99.0;
    score.english = 88;
    son.score = score;
    
    Contact *contact = [[Contact alloc] init];
    contact.phone = @"123456789";
    contact.email = @"feng@gmail";
    son.contact = contact;
    son.name = @"daisuke";
    
    Son *sonCopy = [son copy];
    
    NSLog(@"%@--%@--%@--%@", son, son.score, son.name, son.contact);                      
 // <Son: 0x604000273240>--<Score: 0x6040000335e0>--daisuke--<Contact: 0x604000033600>
    NSLog(@"%@--%@--%@--%@", sonCopy, sonCopy.score, sonCopy.name, sonCopy.contact);               
// <Son: 0x600000460700>--<Score: 0x6000000301c0>--daisuke--<Contact: 0x6000000301a0>

}

這樣sonCopy.name和 sonCopy.contact兩個屬性都有值了液斜,也完成了深復(fù)制,從斷點也可以看到運行了父類的copyWithZone:方法叠穆,并且把父類的相關(guān)屬性進行了深復(fù)制少漆,如下圖顯示:

[圖片上傳失敗...(image-112dc2-1530757237487)]

互相引用進行深拷貝結(jié)果如何?

在很多多情況下痹束,對象都是互相引用的检疫,當(dāng)然一個是strong一個是weak,否則造成循環(huán)引用祷嘶,引起內(nèi)存泄漏屎媳。比如:信用卡必須有一個人的屬性夺溢,而人未必有信用卡的屬性。那么對信用卡進行深復(fù)制烛谊,結(jié)果是如何呢风响?
第一反應(yīng)想到的是首先創(chuàng)建一個People類并擁有一個(weak引用)card屬性,一個Card類并擁有一個(strong引用)people屬性丹禀。然后遵循NSCopying協(xié)議状勤,實現(xiàn)copyWithZone:方法。
結(jié)果發(fā)現(xiàn)在People類中的copyWithZone:方法中不能對card進行copy操作双泪,因為進行copy的時候新對象的引用計數(shù)器會+1持搜,這樣跟weak引用造成沖突:

// People.m
@implementation People

- (id)copyWithZone:(NSZone *)zone{
    People *p = [[self class] allocWithZone:zone];
// 警告:Assigning retained object to weak property; object will be released after assignment
    p.card = [self.card copy];
    return p;
}

@end

警告信息:Assigning retained object to weak property; object will be released after assignment

因為card屬性是weak類型,不能對它進行copy操作焙矛。否則造成死循環(huán)而崩潰葫盼,如下圖顯示:

[圖片上傳失敗...(image-3133a4-1530757237487)]

那么不能進行copy操作,直接賦值會怎樣呢村斟?

// Card.h
@class People;
@interface Card : NSObject<NSCopying>

@property (nonatomic, copy) NSString *number;
@property (nonatomic, strong) People *people;

@end

// Card.m
@implementation Card

- (id)copyWithZone:(NSZone *)zone{
    Card *card = [[self class] allocWithZone:zone];
    card.people = [self.people copy];
    card.number = self.number;
    return card;
}

@end

// People.h
@class Card;
@interface People : NSObject<NSCopying>

@property (nonatomic, weak) Card *card;
@property (nonatomic, copy) NSString *name;
@end

// People.m
@implementation People

- (id)copyWithZone:(NSZone *)zone{
    People *p = [[self class] allocWithZone:zone];
    p.card = self.card;
    p.name = self.name;
    return p;
}

@end

測試:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Card *card = [[Card alloc] init];
    card.number = @"9999999";
    
    People *people = [[People alloc] init];
    people.name = @"daisuke";
    
    card.people = people;
    people.card = card;
    
    Card *cardCopy = [card copy];
    NSLog(@"%@--%@--%@", card, card.people, card.people.card);
    NSLog(@"%@--%@--%@", cardCopy, cardCopy.people, cardCopy.people.card);
    // <Card: 0x60000042b700>--<People: 0x6000004297e0>--<Card: 0x60000042b700>
    // <Card: 0x60000042b640>--<People: 0x60000042bac0>--<Card: 0x60000042b700>
}

從打印信息看出card.people.card贫导、cardCopy.people.card兩個的地址是一樣的:0x60000042b700,而0x60000042b700指向的是card的地址蟆盹,通過一張圖展示他們的關(guān)系:

[圖片上傳失敗...(image-c0ac9e-1530757237487)]

顯然這個不是我們想要的結(jié)果孩灯,想要的結(jié)果如下圖顯示:

[圖片上傳失敗...(image-dfe9d-1530757237487)]

因為對對象進行深復(fù)制,里面的對象屬性也要深復(fù)制逾滥,但是因為對象屬性是weak引用峰档,不允許copy操作,否則造死成循環(huán)而崩潰匣距,怎么辦呢面哥?
由于能力有限,只能做一下簡單處理毅待,如果哪位有好的建議請聯(lián)系我,謝謝归榕。

// Card.m
@implementation Card

- (id)copyWithZone:(NSZone *)zone{
    Card *card = [[self class] allocWithZone:zone];
    card.people = [self.people copy];
    // 重新指向新創(chuàng)建的card
    card.people.card = card;
    card.number = self.number;
    return card;
}

@end

把people的card屬性重新指向新創(chuàng)建的card尸红,進行測試:

- (void)viewDidLoad {
    [super viewDidLoad];

    Card *card = [[Card alloc] init];
    card.number = @"9999999";
    
    People *people = [[People alloc] init];
    people.name = @"daisuke";
    
    card.people = people;
    people.card = card;
    
    Card *cardCopy = [card copy];
    NSLog(@"%@--%@--%@", card, card.people, card.people.card);
    NSLog(@"%@--%@--%@", cardCopy, cardCopy.people, cardCopy.people.card);
    // <Card: 0x60400003a9e0>--<People: 0x60400042d120>--<Card: 0x60400003a9e0>
    // <Card: 0x60400042c220>--<People: 0x60400042b560>--<Card: 0x60400042c220>
 }

顯然這樣就能簡單實現(xiàn)想要的結(jié)果。

基類runtime方式讓所有子類自動實現(xiàn)深復(fù)制操作(有不足之處)

創(chuàng)建基類BaseModel刹泄,有兩種方式實現(xiàn)深復(fù)制:

基類BaseModel遵循NSCopying協(xié)議

// BaseModel.m
- (id)copyWithZone:(NSZone *)zone{
    id object = [[self class] allocWithZone:zone];
    
    unsigned int propertyCount = 0;
    
    objc_property_t *properties = class_copyPropertyList([self class], &propertyCount);
    
    for (int i = 0; i < propertyCount; i++) {
        const char *name = property_getName(properties[I]);
        NSString *propertyName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
        
        NSObject<NSCopying> *tempValue = [self valueForKey:propertyName];
        if (tempValue) {
            // 此處如果是對象屬性外里,且形成閉環(huán)關(guān)系,會造成死循環(huán)導(dǎo)致崩潰特石。
            id value = [tempValue copy];
            [object setValue:value forKey:propertyName];
        }
    }
    return object;
}

使用runtime方式遍歷遞歸對象的屬性而進行深復(fù)制盅蝗,這里有一個弊端,該方式不適合有閉環(huán)方式的對象使用姆蘸,否則造成死循環(huán)墩莫。上面講的互相引用的例子就是這個的證明芙委,關(guān)系如下圖顯示:

[圖片上傳失敗...(image-7d3ad-1530757237487)]

所以使用BaseModel類方式的話,必須確保對象與對象之間沒有形成閉環(huán)關(guān)閉狂秦。那么就測試一下沒有閉環(huán)關(guān)系例子:

新建FatherModel類灌侣、ContactModel類分別繼承于BaseModel類

// FatherModel.h
@interface FatherModel : BaseModel

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
/// 聯(lián)系方式
@property (nonatomic, strong) ContactModel *contact;

@end

// FatherModel.m
@implementation FatherModel

@end

// ContactModel.h
@interface ContactModel : BaseModel

@property (nonatomic, copy) NSString *phone;
@property (nonatomic, copy) NSString *email;

@end

// ContactModel.m
@implementation ContactModel

@end

測試:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    FatherModel *father = [[FatherModel alloc] init];
    father.name = @"daisuke";
    father.age = 26;

    ContactModel *contact = [[ContactModel alloc] init];
    contact.phone = @"123456789";
    contact.email = @"feng@gmail";
    father.contact = contact;

    FatherModel *fatherCopy = [father copy];
    NSLog(@"%@--%@--%@--%@", father, father.name, father.contact.phone, father.contact);
    NSLog(@"%@--%@--%@--%@", fatherCopy, fatherCopy.name,fatherCopy.contact.phone, fatherCopy.contact);
    // <FatherModel: 0x604000035b80>--daisuke--123456789--<ContactModel: 0x604000035ba0>
    // <FatherModel: 0x604000231960>--daisuke--123456789--<ContactModel: 0x6040002312e0>
}

從打印信息中看到實現(xiàn)了深復(fù)制功能。

注意點:對象屬性如果沒有遵循NSCopying協(xié)議裂问,只是簡單賦值侧啼,不會進行深復(fù)制操作。

基類BaseModel遵循NSCoding協(xié)議

// BaseModel.m
@implementation BaseModel

- (void)encodeWithCoder:(NSCoder *)aCoder{
    unsigned int propertyCount = 0;
    
    objc_property_t *properties = class_copyPropertyList([self class], &propertyCount);
    
    for (int i = 0; i < propertyCount; i++) {
        const char *name = property_getName(properties[I]);
        NSString *propertyName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
        
        NSObject *tempValue = [self valueForKey:propertyName];
        if (tempValue && [tempValue conformsToProtocol:@protocol(NSCopying)]) {
            [aCoder encodeObject:tempValue forKey:propertyName];
        }
        
    }
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder{
    self = [super init];
    if (self) {
        unsigned int propertyCount = 0;
        
        objc_property_t *properties = class_copyPropertyList([self class], &propertyCount);
        
        for (int i = 0; i < propertyCount; i++) {
            const char *name = property_getName(properties[I]);
            NSString *propertyName = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
            
            NSObject *tempValue = [aDecoder decodeObjectForKey:propertyName];
            if (tempValue) {
                [self setValue:tempValue forKey:propertyName];
            }
        }
    }
    return self;
}

@end

測試新建CompanyModel類沒有繼承BaseModel堪簿,也沒有遵循NSCoding協(xié)議痊乾。

    FatherModel *father = [[FatherModel alloc] init];
    father.name = @"daisuke";
    father.age = 26;
    
    CompanyModel *company = [[CompanyModel alloc] init];
    company.companyName = @"小公司";
    father.company = company;
    
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:father];
    FatherModel *fatherCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    
    NSLog(@"%@--%@--%@--%@", father, father.name, father.company, father.company.companyName);
    NSLog(@"%@--%@--%@--%@", fatherCopy, fatherCopy.name,fatherCopy.company, fatherCopy.company.companyName);
    // <FatherModel: 0x60400024fbd0>--daisuke--<CompanyModel: 0x6040000086b0>--小公司
    // <FatherModel: 0x60400024fba0>--daisuke--(null)--(null)

從打印信息看到fatherCopy的company是空的。那么讓CompanyModel類繼承BaseModel椭更,然后直接運行:

    FatherModel *father = [[FatherModel alloc] init];
    father.name = @"daisuke";
    father.age = 26;
    
    CompanyModel *company = [[CompanyModel alloc] init];
    company.companyName = @"小公司";
    father.company = company;
    
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:father];
    FatherModel *fatherCopy = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    
    NSLog(@"%@--%@--%@--%@", father, father.name, father.company, father.company.companyName);
    NSLog(@"%@--%@--%@--%@", fatherCopy, fatherCopy.name,fatherCopy.company, fatherCopy.company.companyName);
    
    // <FatherModel: 0x60400025a550>--daisuke--<CompanyModel: 0x604000012240>--小公司
    // <FatherModel: 0x600000257d30>--daisuke--<CompanyModel: 0x6000000076f0>--小公司

發(fā)現(xiàn)屬性有值了且完成了深復(fù)制操作符喝。

聲明屬性是使用copy、strong的區(qū)別甜孤?

新建一個CopyOrStrongModel類协饲。

copy聲明

// 使用copy聲明name屬性:
@interface CopyOrStrongModel : NSObject

@property (nonatomic, copy) NSString *name;

@end

測試:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    CopyOrStrongModel *model = [[CopyOrStrongModel alloc] init];
    model.name = @"daisuke";
    NSLog(@"model.name是否是NSMutableString類型:%@", @([model.name isKindOfClass:[NSMutableString class]]));
    // model.name是否是NSMutableString類型:0
    
}

把值改為是可變類型的:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    CopyOrStrongModel *model = [[CopyOrStrongModel alloc] init];
    
    NSMutableString *stringM = [[NSMutableString alloc] initWithString:@"daisuke"];
    model.name = stringM;

    NSLog(@"%@--%p--%@--%p", model.name, model.name, stringM, stringM);
    // daisuke--0xa656b75736961647--daisuke--0x6040002556c0
    [stringM appendString:@"追加"];
    NSLog(@"%@--%p--%@--%p", model.name, model.name, stringM, stringM);
    // daisuke--0xa656b75736961647--daisuke追加--0x6040002556
    NSLog(@"model.name是否是NSMutableString類型:%@", @([model.name isKindOfClass:[NSMutableString class]]));
    // model.name是否是NSMutableString類型:0
}

發(fā)現(xiàn)雖然stringM是可變字符串,但是進行model.name = stringM;實際是對可變字符串進行了[可變字符串 copy]操作缴川,從而得到的是NSString類型茉稠。

strong聲明

@property (nonatomic, strong) NSString *name;

測試:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    CopyOrStrongModel *model = [[CopyOrStrongModel alloc] init];
    model.name = @"daisuke";
    NSLog(@"%@--%p", model.name, model.name);
    
}

使用這種方式跟copy一樣,但是使用可變字符串賦值又會如何呢把夸?

- (void)viewDidLoad {
    [super viewDidLoad];
    
    CopyOrStrongModel *model = [[CopyOrStrongModel alloc] init];
    
    NSMutableString *stringM = [[NSMutableString alloc] initWithString:@"daisuke"];
    model.name = stringM;
    
    // strong
    NSLog(@"%@--%p--%@--%p", model.name, model.name, stringM, stringM);
    // daisuke--0xa656b75736961647--daisuke--0x6040002556c0
    [stringM appendString:@"追加"];
    NSLog(@"%@--%p--%@--%p", model.name, model.name, stringM, stringM);
    // daisuke追加--0x60000025d460--daisuke追加--0x60000025d46
    NSLog(@"model.name是否是NSMutableString類型:%@", @([model.name isKindOfClass:[NSMutableString class]]));
    // model.name是否是NSMutableString類型:1
}

從打印信息的到如果是strong類型聲明的話而线,可變字符串賦值的后 model.name實際類型變成了NSMutableString類型。這不是我們想要的結(jié)果恋日,顯然對于model來說name屬性的類型不想因為外界的賦值而改變膀篮,也不想因為外界的值改變了,model.name的值也跟著改變岂膳。

總結(jié):

  • 使用NSString賦值誓竿,使用copy或strong聲明都可以
  • 使用NSMutableString賦值,copy聲明不會改變原類型([可變字符串 copy]得到的是NSString類型)谈截,也不會跟隨外界賦的值而改變筷屡。使用strong聲明就會。

重寫Setter方法

當(dāng)然有一種周全的方法簸喂,即便你是copy還是strong聲明毙死,賦值的是NSString還是NSMutableString類型,都不想外界修改的話喻鳄。你可以重寫setName:方法扼倘,例如:

- (void)setName:(NSString *)name{
    NSLog(@"%p", name);
    _name = [name copy];
    NSLog(@"%p", _name);
}

測試:

  • 使用copy方式:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    CopyOrStrongModel *model = [[CopyOrStrongModel alloc] init];
    
    NSMutableString *stringM = [[NSMutableString alloc] initWithString:@"daisuke"];
    model.name = stringM;
    
    // copy
    NSLog(@"%@--%p--%@--%p", model.name, model.name, stringM, stringM);
    // daisuke--0xa656b75736961647--daisuke--0x6040000564d0
    [stringM appendString:@"追加"];
    NSLog(@"%@--%p--%@--%p", model.name, model.name, stringM, stringM);
    // daisuke--0xa656b75736961647--daisuke追加--0x6040000564d0
    NSLog(@"model.name是否是NSMutableString類型:%@", @([model.name isKindOfClass:[NSMutableString class]]));
    // model.name是否是NSMutableString類型:0
}
  • strong方式:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    CopyOrStrongModel *model = [[CopyOrStrongModel alloc] init];
    
    NSMutableString *stringM = [[NSMutableString alloc] initWithString:@"daisuke"];
    model.name = stringM;
    
    // strong
    NSLog(@"%@--%p--%@--%p", model.name, model.name, stringM, stringM);
    // daisuke--0xa656b75736961647--daisuke--0x604000445880
    [stringM appendString:@"追加"];
    NSLog(@"%@--%p--%@--%p", model.name, model.name, stringM, stringM);
    // daisuke--0xa656b75736961647--daisuke追加--0x604000445880
    NSLog(@"model.name是否是NSMutableString類型:%@", @([model.name isKindOfClass:[NSMutableString class]]));
    // model.name是否是NSMutableString類型:0
}

顯然,重寫了setName:方法并對name進行了copy操作除呵,無論是copy還是strong聲明再菊,賦值的是NSString還是NSMutableString類型爪喘,都不會受到外界影響,所以應(yīng)該養(yǎng)成這個習(xí)慣袄简,這樣就萬無一失了腥放。

可變類型

CopyOrStrongModel聲明兩個屬性:

@property (nonatomic, strong) NSMutableString *mutableString1;
@property (nonatomic, copy) NSMutableString *mutableString2;

測試:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    CopyOrStrongModel *model = [[CopyOrStrongModel alloc] init];
    
    NSMutableString *string = @"daisuke".mutableCopy;
    model.mutableString1 = string;
    model.mutableString2 = string;
    
    NSLog(@"strong聲明的model.mutableString1的類型是否是NSMutableString: %@", @([model.mutableString1 isKindOfClass:[NSMutableString class]]));
    // strong聲明的model.mutableString1的類型是否是NSMutableString: 1
    NSLog(@"copy聲明的model.mutableString1的類型是否是NSMutableString: %@", @([model.mutableString2 isKindOfClass:[NSMutableString class]]));
    // copy聲明的model.mutableString1的類型是否是NSMutableString: 0
    
}

注意:使用copy聲明NSMutableString類型的屬性,實際得到的是NSString類型

demo

demo

參考文檔

聲明

  • 以上內(nèi)容如有侵權(quán)绿语,請聯(lián)系更正
  • 轉(zhuǎn)載請注明出處
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秃症,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子吕粹,更是在濱河造成了極大的恐慌种柑,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匹耕,死亡現(xiàn)場離奇詭異聚请,居然都是意外死亡,警方通過查閱死者的電腦和手機稳其,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門驶赏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人既鞠,你說我怎么就攤上這事煤傍。” “怎么了嘱蛋?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵蚯姆,是天一觀的道長。 經(jīng)常有香客問我洒敏,道長龄恋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任凶伙,我火速辦了婚禮郭毕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘镊靴。我一直安慰自己铣卡,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布偏竟。 她就那樣靜靜地躺著,像睡著了一般敞峭。 火紅的嫁衣襯著肌膚如雪踊谋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天旋讹,我揣著相機與錄音殖蚕,去河邊找鬼轿衔。 笑死,一個胖子當(dāng)著我的面吹牛睦疫,可吹牛的內(nèi)容都是我干的害驹。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼蛤育,長吁一口氣:“原來是場噩夢啊……” “哼宛官!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瓦糕,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤底洗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后咕娄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亥揖,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年圣勒,在試婚紗的時候發(fā)現(xiàn)自己被綠了费变。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡圣贸,死狀恐怖挚歧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情旁趟,我是刑警寧澤昼激,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站锡搜,受9級特大地震影響橙困,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜耕餐,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一凡傅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肠缔,春花似錦夏跷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至趟妥,卻和暖如春猫态,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工亲雪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留勇凭,地道東北人。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓义辕,卻偏偏與公主長得像虾标,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子灌砖,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容