第三章 接口與API設(shè)計(jì)
一份好的代碼呼股,不光自己能夠看懂,也應(yīng)該讓別人很容易理解画恰,并且我們要確保代碼添加的別的工程中的時(shí)候彭谁,不會(huì)影響其他代碼,這時(shí)候允扇,好的接口和API設(shè)計(jì)就非常有用了缠局。
15. 用前綴避免命名空間沖突
簡單的說就是不能在同一個(gè)工程中出現(xiàn)相同的類名,解決的辦法就是加前綴考润。Apple宣稱保留所有“兩字母前綴”狭园,所以大部分代碼的前綴都是三個(gè)大寫字母,這些字母可以是任意的糊治,可以根據(jù)公司名唱矛、項(xiàng)目名等。應(yīng)用程序中所有的名稱都應(yīng)該加前綴井辜,包括“分類”及“分類”中的方法等绎谦。
另外需要注意的一點(diǎn),我們在寫一份第三方應(yīng)用的時(shí)候抑胎,如果在我們的文件中引入了其他第三方代碼燥滑,一定要給第三方代碼加前綴,雖然這是一個(gè)很枯燥的過程阿逃,因?yàn)橐梦覀兇a的人可能也引入了那個(gè)第三方代碼铭拧,這個(gè)時(shí)候就會(huì)起沖突赃蛛,所以這一點(diǎn)一定要注意,例如Reachability文件的引用搀菩。
16. 提供“全能初始化方法”
對象的產(chǎn)生需要初始化呕臂,有時(shí)候一個(gè)類可能存在多個(gè)初始化方法,這樣做可以讓我們根據(jù)自己的需求創(chuàng)建出我們想要點(diǎn)的實(shí)例對象肪跋,不過這樣做歧蒋,我么要在這些初始化方法中選擇一個(gè)“全能初始化方法”,令其他的初始化方法都來調(diào)用發(fā)州既。全能初始化方法的定義是這樣的谜洽,我們把這種可為對象提供必要信息以便其能完成工作的初始化方法,叫做全能初始化方法吴叶。
其實(shí)運(yùn)用全能初始化方法的最直觀的好處就是阐虚,當(dāng)?shù)讓訑?shù)據(jù)存儲(chǔ)機(jī)制變動(dòng)的時(shí)候,只要修改全能初始化方法就可以了蚌卤。
下面用例子說明如何創(chuàng)建一個(gè)類的全能初始化方法实束,以及如何創(chuàng)建這個(gè)類的子類的全能初始化方法:
首先定義一個(gè)矩形類:
.h文件
@interface EOCRectangle : NSObject
@property (nonatomic, assign, readonly) float width;
@property (nonatomic, assign, readonly) float height;
- (id)initWithWidth:(float)width height:(float)height;
@end
.m文件
@implementation EOCRectangle
- (id)initWithWidth:(float)width height:(float)height{
if ((self = [super init])) {
_width = width;
_height = height;
}
return self;
}
@end
只這么寫還是不行的,因?yàn)橛袝r(shí)候可能會(huì)用[[EOCRectangle alloc] init]
的方法創(chuàng)建實(shí)例(這個(gè)方法是從NSObject繼承過來的)逊彭,這時(shí)候我們應(yīng)該覆寫init初始化方法咸灿,如下:
// 第一種方案
- (instancetype)init{
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"必須用initWithWidth:Height: 初始化方法" userInfo:nil];
}
// 第二種方案
- (instancetype)init{
return [self initWithWidth:5.0 height:10.0];
}
個(gè)人推薦第一種方案,因?yàn)榈诙N方案在底層數(shù)據(jù)變動(dòng)的時(shí)候還是要修改侮叮,并且我們可能不想要一個(gè)默認(rèn)的值避矢。
下面定義一個(gè)正方形類EOCSquare,繼承于上面的矩形類:
.h文件
@interface EOCSquare : EOCRectangle
- (id)initWithDimension:(float)dimension;
@end
.m文件
@implementation EOCSquare
- (id)initWithDimension:(float)dimension{
return [super initWithWidth:dimension height:dimension];
}
@end
這樣寫很合理签赃,在子類的全能初始化方法中調(diào)用了父類全能初始化方法谷异,我們在繼承體系中,一定剛要確保這樣的鏈?zhǔn)浇Y(jié)構(gòu)延續(xù)下去锦聊,這個(gè)時(shí)候我們還可能用initWithWidth:Height:
和init
方法初始化EOCSquare類歹嘹,這時(shí)候我們還要覆寫這兩個(gè)方法,如下:
覆寫initWithWidth:Height:
方法
// 第一種方案
- (id)initWithWidth:(float)width height:(float)height{
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"必須用initWithDimension:初始化方法" userInfo:nil];
}
// 第二種方案
- (id)initWithWidth:(float)width height:(float)height{
float dimension = MAX(width, height);
return [self initWithDimension:dimension];
}
個(gè)人還是推薦第一種方案
覆寫init
方法
// 第一種方案
- (instancetype)init
{
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"必須用initWithDimension:初始化方法" userInfo:nil];
}
// 第二種方案
- (instancetype)init
{
return [self initWithDimension:0.5];
}
個(gè)人還是推薦第一種方案
有時(shí)候孔庭,一個(gè)類的實(shí)例可能來自完全不同的兩種初始化方式尺上,例如我們上面定義的矩形類,如果遵循了NSCoding協(xié)議圆到,那么我們就要另外添加一種全能初始化方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super init])) {
_width = [aDecoder decodeFloatForKey:@"width"];
_height = [aDecoder decodeFloatForKey:@"height"];
}
return self;
}
一個(gè)類的父類遵循了NSCoding協(xié)議怎抛,按照上面的例子,此時(shí)若EOCSquare類也遵循NSCoding協(xié)議的話芽淡,應(yīng)該也增加全能化初始化方法马绝,并且可以調(diào)用父類的全能初始化方法,如下:
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super initWithCoder:aDecoder])) {
// 處理子類自己的需求
}
return self;
}
我們始終要遵循子類的全能初始化方法調(diào)用父類全能初始化方法這一規(guī)定挣菲,例如本例中富稻,如果不調(diào)用EOCRectangle的initWithCoder:
方法的話掷邦,就無法將_width
和_height
兩個(gè)實(shí)例變量解碼。
17. 實(shí)現(xiàn)description方法
description方法定義在NSObject協(xié)議里椭赋,NSObject也實(shí)現(xiàn)了他抚岗,所以如果不在類里覆寫description方法,打印信息就會(huì)調(diào)用NSObject類實(shí)現(xiàn)的默認(rèn)方法(NSProxy基類也遵循NSObject協(xié)議)哪怔。description方法主要的用處就是在打印信息的時(shí)候調(diào)用這個(gè)方法獲取信息宣蔚,如果不在自己的類里覆寫description方法,打印出來的信息只有類名和內(nèi)存地址认境,很顯然這不能滿足我們的需求胚委,這時(shí)候就要覆寫description方法,增加對象的描述信息元暴。
下面提幾個(gè)點(diǎn):
1.在新實(shí)現(xiàn)的description方法中篷扩,也應(yīng)該像默認(rèn)實(shí)現(xiàn)的那樣,打印出類名和內(nèi)存地址茉盏。
2.在自定義信息內(nèi)容的時(shí)候可以遵循字典那樣的格式,這樣方便以后增加和刪除打印項(xiàng)枢冤,并且字典形式看起來更簡潔鸠姨。
不過怎么定義打印信息全看我們自己,沒有固定的格式淹真,只要自己用著方便就好讶迁。
NSObject協(xié)議中還有一個(gè)debugDescription方法,這個(gè)方法是開發(fā)者在調(diào)試器中以控制臺(tái)命令打印對象調(diào)用的(就是我們在控制臺(tái)中輸入po時(shí)調(diào)用)核蘸,在NSObject類的默認(rèn)實(shí)現(xiàn)中debugDescription方法直接調(diào)用description方法巍糯,和description方法的覆寫一樣,具體怎么設(shè)置打印信息沒有具體標(biāo)準(zhǔn)客扎,只要自己用著方便就行祟峦。
18. 盡量使用不可變對象
設(shè)計(jì)類的時(shí)候,應(yīng)該盡量用屬性來封裝數(shù)據(jù)徙鱼,而在使用屬性時(shí)宅楞,則應(yīng)該盡量將屬性聲明為只讀(readonly),為什么要這樣呢袱吆?通常一個(gè)類中數(shù)據(jù)都是由網(wǎng)絡(luò)獲取的厌衙,即使我們修改這些屬性,也不會(huì)發(fā)送回服務(wù)器绞绒,所以沒有必要婶希,另外,有的時(shí)候我們不知道屬性的內(nèi)部結(jié)構(gòu)蓬衡,比如集合中是否包含可變對象等喻杈,如果包含彤枢,這些可變對象是否可以更改,這些都是未知的奕塑,所以我們在設(shè)計(jì)屬性的時(shí)候盡量都聲明為只讀堂污,這樣可以避免很多麻煩,當(dāng)然這不是固定的龄砰,真正的開發(fā)中還是看實(shí)際的需要盟猖,只是在多數(shù)情況下建議這樣做。
為了把屬性對外設(shè)置成只讀换棚,通常將readonly的屬性在對象內(nèi)部聲明為readwrite式镐,通常都是在分類中從新聲明一下,不過這么做需要注意一點(diǎn)固蚤,當(dāng)屬性是nonatomic的時(shí)候娘汞,可能產(chǎn)生“競爭條件”(內(nèi)部寫入屬性時(shí),外部也許正在讀取屬性)夕玩,若想避免這個(gè)問題可能在必要時(shí)通過“派發(fā)隊(duì)列”手段你弦,將所有數(shù)據(jù)存儲(chǔ)操作設(shè)為同步操作。
需要提一點(diǎn)的是燎孟,即使屬性聲明為readonly禽作,值也是可以通過外部修改的,可以通過KVC直接進(jìn)行鍵值編碼揩页,更暴力一點(diǎn)可以直接用類型信息查詢功能查出屬性所多對應(yīng)的實(shí)例變量在內(nèi)存布局中的偏移量旷偿,以此來人為設(shè)置這個(gè)實(shí)例變量的值,不過這么做都是不推薦的爆侣。
另外還需要注意一點(diǎn)萍程,當(dāng)我們的屬性是集合類型的時(shí)候,我們應(yīng)該將屬性設(shè)置成可變還是不可變兔仰,通過下面例子說明:
.h文件
@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, copy, readonly) NSSet *friends;
- (id)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName;
- (void)addFiriend:(EOCPerson *)person;
- (void)removeFiriend:(EOCPerson *)person;
@end
.m文件
@interface EOCPerson ()
@property (nonatomic, copy, readwrite) NSString *firstName;
@property (nonatomic, copy, readwrite) NSString *lastName;
@end
@implementation EOCPerson{
NSMutableSet *_internalFriends;
}
- (id)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName{
if ((self = [super init])) {
_firstName = firstName;
_lastName = lastName;
_internalFriends = [NSMutableSet new];
}
return self;
}
- (void)addfriend:(EOCPerson *)person{
[_internalFriends addObject:person];
}
- (void)removefriend:(EOCPerson *)person{
[_internalFriends removeObject:person];
}
- (NSSet *)friends{
return [_internalFriends copy];
}
@end
注意這個(gè)例子中的friends屬性茫负,有人可能會(huì)問,為什么不用NSMutableSet來實(shí)現(xiàn)friensds屬性呢斋陪?這樣做還可以省去addFriend:和removeFriend:兩個(gè)方法朽褪,我們要知道一點(diǎn),如果換成NSMutableSet无虚,這種過分解耦的數(shù)據(jù)很容易出現(xiàn)bug缔赠,如果換成NSMutableSet省去添加朋友和刪除朋友這兩個(gè)方法,那么相當(dāng)于從底層直接修改了內(nèi)部存放朋友對象的set友题,在EOCPerson對象不知情時(shí)嗤堰,很容易使對象內(nèi)個(gè)數(shù)據(jù)之間不一致。
19. 使用清晰而協(xié)調(diào)的命名方式
首次接觸OC的人都認(rèn)為OC的命名太長了,可能有的人喜歡有的人不喜歡踢匣,不過一種形式設(shè)計(jì)出來并且大家都在遵守就必然有其存在的理由。
和OC接觸多了我們會(huì)發(fā)現(xiàn)离唬,大家的代碼基本都遵循這樣的一套規(guī)范后专,方法與變量名使用“駝峰命名法”(以小寫字母開頭,其后每個(gè)單詞首字母大寫)输莺,類名也用駝峰命名法戚哎,不過首字母大寫,而且通常有前綴字母嫂用。
對于方法的命名來說型凳,最好能答到的目標(biāo)是開發(fā)者能根據(jù)方法名知道這個(gè)方法有什么作用,并且了解其中的各個(gè)參數(shù)所表達(dá)的具體意思嘱函,新手可能不習(xí)慣寫這種長的方法名甘畅,但是慢慢的就會(huì)喜歡這種命名方式,不過有一點(diǎn)就是寫習(xí)慣OC代碼的人往弓,對其他的語言可能會(huì)很不習(xí)慣疏唾,因?yàn)樗麄儗Ψ椒a(chǎn)生了依賴性。
類與協(xié)議名通常要加前綴函似,以避免命名沖突荸实,這在前面已經(jīng)說過,另外我們可以模仿UIKit類庫的整體命名體系缴淋,模仿源代碼總是不會(huì)錯(cuò)的。
20. 為私有方法名加前綴
可能有人會(huì)說泄朴,私有方法為什么還要加前綴重抖,私有方法又不是暴露在外面,只要自己用著方便就行祖灰,其實(shí)為私有方法加前綴也是有好處的钟沛。為私有方法加固定的前綴可以將私有方法和其他的區(qū)分開,方便查找修改局扶,另外多提一點(diǎn)恨统,私有方法盡量寫在一起。另外注意一點(diǎn)三妈,不要模仿蘋果用單一下劃線作為私有方法的前綴柑司,并且這樣做也是蘋果不推薦的籍嘹,因?yàn)橛袝r(shí)候我們可能繼承蘋果原有的類寫了一個(gè)子類,如果這樣命名的話很可能無意間覆寫了父類的方法÷兹裕總之我們最好保證以下兩點(diǎn)為私有方法命名,第一保證自己的私有方法名是獨(dú)一無二的,第二盡量使自己的私有方法方便查找。
21. 理解Objective_C錯(cuò)誤模型
OC有自己的異常信息處理方式掩宜,通常有三種方法。
1.拋出異常
主要用的是exceptionWithName: reason: userInfo:
方法么翰,將錯(cuò)誤信息標(biāo)出牺汤,然后拋出異常。這種處理只應(yīng)該應(yīng)用于極其嚴(yán)重的錯(cuò)誤浩嫌,比如前面舉例的利用了不該用的初始化方法檐迟,這種拋出異常的方法還是不建議用的,因?yàn)檫@樣寫的代碼很有可能因?yàn)閽伋霎惓6兊牟话踩谈茫缦旅娴睦樱?/p>
id someResource = /*···*/;
if (/*check for error*/) {
@throw [NSException exceptionWithName:ExceptionName reason:@"There was an error" userInfo:nil];
}
[someResource doSomething];
[someResource release];
從例子中可以看出當(dāng)拋出異常之后后面的釋放語句沒有執(zhí)行锅减,這樣寫的代碼是不安全的,在ARC下可以通過修改編譯器標(biāo)志避免這種情況(打開編譯器標(biāo)志叫做-fobjc-arc-exceptions)伐坏,但是這樣會(huì)讓程序運(yùn)行不必要的代碼怔匣。在非ARC下我們可以手動(dòng)把釋放代碼添加都前面,但是當(dāng)代碼結(jié)構(gòu)復(fù)查的時(shí)候顯然有很大的弊端桦沉,所以拋出異常這種做法只應(yīng)該在很嚴(yán)重的情況下應(yīng)用每瞒,并且異常拋出后無須考慮修復(fù),程序直接退出纯露。
2.令返回值為nil/0
這是更不推薦的一種做法剿骨,范式就是通過條件判斷語句,在不能達(dá)到我們要求的情況的時(shí)候返回nil/0埠褪,這種方法是極力不推薦的浓利,因?yàn)檫@種代碼有時(shí)候會(huì)讓給我們造成一些不必要的困擾。
3.使用NSError
這種方法是推薦的钞速,并且NSError用法靈活贷掖,經(jīng)由這種方法,可以把導(dǎo)致錯(cuò)誤的原因回報(bào)給調(diào)用者渴语,讓調(diào)用者按照錯(cuò)誤信息查找原因苹威。
Error對象內(nèi)部通常會(huì)封裝三種信息:
- Error domain(錯(cuò)誤范圍,類型為字符串)
產(chǎn)生錯(cuò)誤的根源驾凶,通常用特有的全局變量來定義牙甫,比如NSURLErrorDomain。 - Error code(錯(cuò)誤碼调违,類型為整數(shù))
獨(dú)有的錯(cuò)誤代碼窟哺,用于指明某個(gè)特定范圍可能發(fā)生的一系列錯(cuò)誤,這些錯(cuò)誤通常采用枚舉定義翰萨,最長見得就是我們通吃啻穑看到的HTTP請求出錯(cuò)時(shí)的狀態(tài)碼。 - User info(用戶信息,類型為字典)
有關(guān)錯(cuò)誤的一些附加信息殖告,包含錯(cuò)誤的描述阿蝶,或許還含有導(dǎo)致該錯(cuò)誤發(fā)生的另外一個(gè)錯(cuò)誤,經(jīng)由這些信息黄绩,可以將相關(guān)錯(cuò)誤串成一條“錯(cuò)誤鏈”羡洁。
通常在寫代碼的時(shí)候,最常用的方法是通過委托協(xié)議來傳遞NSError對象爽丹,這樣可以把錯(cuò)誤模型傳遞給其他委托對象筑煮,這樣委托對象可以根據(jù)需要判斷是不是需要處理這個(gè)錯(cuò)誤信息,相信這種方式大家都比較熟悉粤蝎,這里不再舉例真仲。
另一種方法是將NSError對象經(jīng)由方法的“輸出參數(shù)”返回給調(diào)用者,范式通常如下:
-(BOOL)doSomething:(NSError**)error
參數(shù)是個(gè)指針初澎,該指針本身指向另外一個(gè)指針秸应,那個(gè)指針指向NSError對象”纾可以通過如下的例子把NSError對象傳遞到輸出參數(shù)中:
-(BOOL)doSomething:(NSError **)error{
// Do something that may cause an error
if (/*There was an error*/) {
if (error) {
// Pass the 'error' through the out-parameter
*error = [NSError errorWithDomain:domain code:code userInfo:userInfo];
}
return NO;
} else {
return YES;
}
}
代碼中通過*error
語法為參數(shù)error
“解引用”软啼,也就是說error
所指的那個(gè)指針現(xiàn)在要指向一個(gè)新的NSError對象了。里面用了一個(gè)判斷語句延柠,這樣做的目的是如果我們對一個(gè)空指針“解引用”會(huì)造成程序崩潰祸挪,所以要保護(hù)一下,因?yàn)橛械臅r(shí)候調(diào)用者可能不關(guān)系具體錯(cuò)誤贞间,會(huì)給error
參數(shù)傳nil贿条。
NSError對象的內(nèi)部錯(cuò)誤信息可以如下定義:
.h文件
extern NSString *const ErrorDomain;
typedef NS_ENUM(NSUInteger, Error){
ErrorUnknow = -1,
ErrorInternalInconsistency = 100,
ErrorGeneralFault = 105,
ErrorBadInput = 500,
};
.m 文件
NSString *const ErrorDomain = @"ErrorDomain";
枚舉中可以標(biāo)注出相應(yīng)的錯(cuò)誤意思,至于userInfo就看錯(cuò)誤的情況自行定義了增热。
22. 理解NSCopying協(xié)議
注意是NSCopying不是NSCoding協(xié)議
OC中對象如果要是能被copy闪唆,就要實(shí)現(xiàn)NSCopying協(xié)議,該協(xié)議只有一個(gè)方法:
- (id)copyWithZone:(NSZone *)zone;
首先不用糾結(jié)zone這個(gè)參數(shù)钓葫,以前開發(fā)程序時(shí),會(huì)根據(jù)NSZone把內(nèi)存分成不同的區(qū)票顾,而對象會(huì)創(chuàng)建在區(qū)里面础浮,現(xiàn)在不用了,現(xiàn)在是每個(gè)程序只在一個(gè)默認(rèn)區(qū)奠骄。下面看一下NSCopying協(xié)議的具體實(shí)現(xiàn)豆同,還是以EOCPerson為例:
.h文件
@interface EOCPerson : NSObject<NSCopying>
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
- (id)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName;
@end
.m文件(實(shí)現(xiàn)協(xié)議中方法)
- (id)copyWithZone:(NSZone *)zone{
EOCPerson *copy = [[[self class] allocWithZone:zone] initWithFirstName:_firstName andLastName:_lastName];
return copy;
}
這是一個(gè)最基本的實(shí)現(xiàn)NSCopying的例子(注意我們直接把拷貝對象交給了“全能初始化方法”),看下面一種情況含鳞,假如EOCPerson類中有一個(gè)集合影锈,該集合和朋友的添加和刪除有關(guān)
.h文件
@interface EOCPerson : NSObject<NSCopying>
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
- (id)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName;
- (void)addFiriend:(EOCPerson *)person;
- (void)removeFiriend:(EOCPerson *)person;
@end
.m文件
@implementation EOCPerson{
NSMutableSet *_internalFriends;
}
- (id)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName{
if ((self = [super init])) {
_firstName = firstName;
_lastName = lastName;
_internalFriends = [NSMutableSet new];
}
return self;
}
- (void)addFiriend:(EOCPerson *)person{
[_internalFriends addObject:person];
}
- (void)removeFiriend:(EOCPerson *)person{
[_internalFriends removeObject:person];
}
- (id)copyWithZone:(NSZone *)zone{
EOCPerson *copy = [[[self class] allocWithZone:zone] initWithFirstName:_firstName andLastName:_lastName];
copy->_internalFriends = [_internalFriends mutableCopy];
return copy;
}
@end
注意這里使用的->
語法,因?yàn)閕nternalFriends不是屬性,只是一個(gè)實(shí)力變量鸭廷,我們令copy對象的internalFriends實(shí)例變量指向這個(gè)復(fù)制過的集合枣抱。我們也可以聲明一個(gè)屬性,但是在這里internalFriends不對外使用辆床,所以沒必要這么做佳晶。這里提一下為什么要拷貝internalFriends實(shí)例變量,而不是讓兩個(gè)對象共享一個(gè)集合讼载,這樣做顯然是不行的轿秧,如果公用一個(gè),那么改變其中一個(gè)對象咨堤,另外一個(gè)對象就隨之改變菇篡,這是我們不想要的。另外一喘,如果本例中的集合是不可變集合驱还,那么就不用復(fù)制。上面的兩個(gè)例子都是用的“全能初始化方法”津滞,這么做不是必須的铝侵,有時(shí)候可能不適用,比如全能初始化方法中涉及到復(fù)雜的數(shù)據(jù)結(jié)構(gòu)触徐,而拷貝后的對象內(nèi)部數(shù)據(jù)可能沒必要這么復(fù)雜咪鲜。
上面的例子中有一個(gè)方法[_internalFriends mutableCopy]
,通過這個(gè)方法引出一個(gè)叫NSMutableCopying的協(xié)議撞鹉,這個(gè)協(xié)議與NSCopying類似疟丙,也只有一個(gè)方法:
-(id)mutableCopyWithZone:(NSZone *)zone;
在解釋NSMutableCopying之前我們要知道,一個(gè)類的可變和不可變版本要遵循下面的規(guī)則鸟雏,以NSArray和NSMutableArray為例:
[NSArray mutableCopy] => NSMutableArray
[NSMutableArray copy] => NSArray
通過上面的規(guī)則享郊,在結(jié)合實(shí)際情況,我們就可以實(shí)現(xiàn)NSMutableCopying協(xié)議孝鹊,這里不再多舉例炊琉。
還有一個(gè)深拷貝和淺拷貝的問題,有時(shí)候我們要考慮是不是要給一個(gè)類添加深拷貝方法deepCopy又活,特別是容器類苔咪,例如NSSet類中就有一個(gè)方法:
- (instancetype)initWithSet:(NSSet<ObjectType> *)set copyItems:(BOOL)flag;
當(dāng)參數(shù)flag為YES的時(shí)候,該方法會(huì)向集合中的每個(gè)元素發(fā)送copy消息柳骄,用拷貝好的元素創(chuàng)建新集合团赏,并返回給調(diào)用者,這個(gè)時(shí)候我們就要考慮編寫一個(gè)deepCopy方法耐薯,以EOCPerson類為例舔清,添加深拷貝方法:
- (id)deepCopy{
EOCPerson *copy = [[[self class] alloc] initWithFirstName:_firstName andLastName:_lastName];
copy->_internalFriends = [[NSMutableSet alloc] initWithSet:_internalFriends copyItems:YES];
return copy;
}
深拷貝沒有具體的協(xié)議丝里,在寫的時(shí)候我們要依照具體的類來決定,另外需要注意一點(diǎn)体谒,在執(zhí)行NSCopying協(xié)議的類中杯聚,大部分默認(rèn)情況下都是執(zhí)行的淺拷貝,深拷貝只在特殊需要時(shí)才會(huì)提出來营密。
最后科普一下械媒,深拷貝和淺拷貝區(qū)別就是,深拷貝會(huì)將對象的底層數(shù)據(jù)也一起拷貝评汰,淺拷貝只拷貝容器對象自身纷捞,對其內(nèi)部數(shù)據(jù)不進(jìn)行拷貝,F(xiàn)oundation框架中默認(rèn)都是淺拷貝被去。