Effective Objective-C 2.0 讀書筆記三

第三章 接口與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)都是淺拷貝被去。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末主儡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子惨缆,更是在濱河造成了極大的恐慌糜值,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坯墨,死亡現(xiàn)場離奇詭異寂汇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)捣染,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門骄瓣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人耍攘,你說我怎么就攤上這事榕栏。” “怎么了蕾各?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵扒磁,是天一觀的道長。 經(jīng)常有香客問我式曲,道長妨托,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任吝羞,我火速辦了婚禮始鱼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘脆贵。我一直安慰自己,他們只是感情好起暮,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布卖氨。 她就那樣靜靜地躺著会烙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪筒捺。 梳的紋絲不亂的頭發(fā)上柏腻,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音系吭,去河邊找鬼五嫂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肯尺,可吹牛的內(nèi)容都是我干的沃缘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼则吟,長吁一口氣:“原來是場噩夢啊……” “哼槐臀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起氓仲,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤水慨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后敬扛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晰洒,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年啥箭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谍珊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捉蚤,死狀恐怖抬驴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缆巧,我是刑警寧澤布持,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站陕悬,受9級特大地震影響题暖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捉超,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一胧卤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拼岳,春花似錦枝誊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绝骚。三九已至,卻和暖如春祠够,著一層夾襖步出監(jiān)牢的瞬間压汪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工古瓤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留止剖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓落君,卻偏偏與公主長得像穿香,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子叽奥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

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