《Effective Objective-C 2.0》第一份讀書筆記

聲明:這個(gè)筆記的系列是我每天早上打開電腦第一件做的事情像樊,當(dāng)然使用的時(shí)間也不是很多因?yàn)檫€有其他的事情去做汽煮,雖然吧自己買了紙質(zhì)的書但是做筆記和看的時(shí)候基本都是看的電子版本鬼癣,一共52個(gè)Tip每一個(gè)Tip的要點(diǎn)我是完全謄寫下來的捞蛋,害怕自己說的不明白所以就謄寫也算是加強(qiáng)記憶埋虹,我會(huì)持續(xù)修改把自己未來遇到的所有相關(guān)的點(diǎn)都加進(jìn)去甫贯,最后希望讀者尊重原著,購(gòu)買正版書籍。PS:不要打賞要喜歡~

Demo:GitHub代碼網(wǎng)址笆制,大大們給個(gè)鼓勵(lì)Star啊。

整個(gè)系列筆記目錄

《Effective Objective-C 2.0》第一份讀書筆記
《Effective Objective-C 2.0》第二份讀書筆記
《Effective Objective-C 2.0》第三份讀書筆記

第一章 熟悉Objective-C

1.了解Objective-C語(yǔ)言的起源

Objective-C 起源自Smalltalk的涣达,也就是消息性語(yǔ)言在辆。而不是函數(shù)調(diào)用

[obj perform:parameter1 and:parameter2];  消息語(yǔ)言
obj->perform(parameter1,paramter2);  函數(shù)調(diào)用方式

關(guān)鍵區(qū)別在于:消息語(yǔ)言最終執(zhí)行的代碼由運(yùn)行環(huán)境決定,而函數(shù)調(diào)用則是由編譯器決定度苔。這也叫做“動(dòng)態(tài)綁定(dynamic binding)”
我們創(chuàng)建一個(gè)OC對(duì)象的時(shí)候可以這樣:

NSString * someString = @“new string ”;

中間的星號(hào)代表的就是指針匆篓,所以我們創(chuàng)建的是一個(gè)someString指針指向一個(gè)NSString的對(duì)象,我們把someString指針對(duì)象放在棧(stack)中,而真正的指針對(duì)象"new string"我們放在堆(heap space)中寇窑。如果我們新建一個(gè)新的指針anyString的數(shù)值仍然是"new string"鸦概,那么就不會(huì)重新在堆中新建內(nèi)存而是直接吧新的anyString指向已經(jīng)創(chuàng)建的"new string"對(duì)象。

兩個(gè)指針指向相同堆數(shù)據(jù).png

分配在堆中的內(nèi)存需要程序員直接管理甩骏,而分配在棧上的會(huì)在棧彈出時(shí)自動(dòng)清理窗市。

Objective-C將堆內(nèi)存管理抽象出來了,不需要用malloc及free來分配或釋放對(duì)象所占內(nèi)存饮笛。Objective-C運(yùn)行期環(huán)境把這部分工作抽象為一套內(nèi)存管理框架咨察,名為“引用計(jì)數(shù)”,
在OC代碼中也會(huì)有使用棧(stack)空間的變量福青,比如不帶星號(hào)的Sturck對(duì)象摄狱,比如CGRect,如果只是簡(jiǎn)單的高度无午,寬度等的數(shù)據(jù)的時(shí)候二蓝,就不用建立OC對(duì)象了會(huì)耗費(fèi)更多額外的開銷。

要點(diǎn):
  • Objective-C為C語(yǔ)言添加了面向?qū)ο筇匦灾秆幔瞧涑蕖bjective-C使用動(dòng)態(tài)綁定的消息結(jié)構(gòu),也就是說踩验,在運(yùn)行時(shí)才會(huì)檢查對(duì)象類型鸥诽。接受一條消息之后商玫,究竟應(yīng)執(zhí)行何種代碼,由運(yùn)行期環(huán)境而非編譯器來決定牡借。
  • 理解C語(yǔ)言的核心概念有助于寫好Objective-C程序拳昌。尤其要掌握內(nèi)存模型和指針。

2.在類的頭文件中盡量少引入其他頭文件

這里面講了為什么使用@class “EOCEmployer.h”這樣的寫法钠龙。
這叫做“向前聲明(forward declaring )” 這是因?yàn)椴恍枰繣OCEmployer的內(nèi)部接口細(xì)節(jié)炬藤。

要點(diǎn):
  • 除非確有必要,否則不要引入頭文件碴里。一般來說沈矿,應(yīng)在某個(gè)類的頭文件中使用向前聲明來體積別的類,并在實(shí)現(xiàn)文件中引入那些類的頭文件咬腋。這樣做可以盡量降低類之間的耦合(coupling)羹膳。
  • 有時(shí)無法使用向前聲明,比如要聲明某個(gè)類遵守一項(xiàng)協(xié)議根竿。這種情況下陵像,盡量把“該類遵守某協(xié)議”的這條聲明移至"class-continuation分類"中。如果不行的話寇壳,就把協(xié)議單獨(dú)放在一個(gè)頭文件中醒颖,然后將其引入。

3.多用字面量語(yǔ)法壳炎,少用與之等價(jià)的方法

我們可以通過“字符串自變量(string literal)”語(yǔ)法來創(chuàng)建對(duì)象:

NSString * someString = @“Effective Objective-C 2.0”; 

使用字面量語(yǔ)法可以縮減源代碼長(zhǎng)度泞歉,使其更為易讀。
有的時(shí)候需要把整數(shù)冕广、浮點(diǎn)數(shù)疏日、布爾值封入Objective-C對(duì)象中偿洁。這種情況下可以用NSNumber類撒汉,該類可處理多種類型的數(shù)值。若是不用字面量涕滋,那么就需要按下述方式創(chuàng)建實(shí)例:

NSNumber * someNumber = [NSNumber numberWithInt:1];
NSNumber * someNumber = @1;
NSNumber * boolNumber = @YES;
NSNumber * charNumber = @‘a(chǎn)’;
NSNumber * expressionNumber = @(x * y);

關(guān)于NSArray的一個(gè)字面量問題:

id object1 = /********/;
id object2 = /********/;
id object3 = /********/;
NSArray * arrayA =[NSArray  arrayWithObjects:object1,object2,object3,nil];
NSArray * arrayB =@[object1,object2,object3];

如果object1和object3 都是指向正常的對(duì)象睬辐,而object2 是nil。那么按照字面量語(yǔ)法創(chuàng)建數(shù)組arrayB時(shí)會(huì)拋出異常宾肺,而arrayA雖然能創(chuàng)建出來溯饵,但是只有一個(gè)object1一個(gè)對(duì)象∠怯茫”arrayWithObjects”方法會(huì)依次處理各個(gè)參數(shù)丰刊,知道發(fā)現(xiàn)nil為止,由于object2是nil增拥,所以該方法會(huì)提前結(jié)束啄巧。所以arrayB更加安全寻歧,直接彈出要比你完美運(yùn)行后來少一個(gè)數(shù)據(jù)來的更安全。

小缺點(diǎn):

使用字面量語(yǔ)法創(chuàng)建的字符串秩仆,數(shù)組码泛,字典對(duì)象都是不可變的(immutable)。若想要可變版本的澄耍,就需要復(fù)制一份:

NSMutableArray * mutable = [[@1,@2,@3,@4,@5] mutableCopy];

這么做會(huì)多調(diào)用一個(gè)辦法噪珊,但是好處是要多余上面的那個(gè)缺少數(shù)據(jù)的缺點(diǎn)的。

要點(diǎn):
  • 應(yīng)該使用字面量語(yǔ)法來創(chuàng)建字符串齐莲,數(shù)值痢站,數(shù)組,字典铅搓。與創(chuàng)建此類對(duì)象的常規(guī)方法相比瑟押,這么做更加簡(jiǎn)單扼要。
  • 應(yīng)該通過取下標(biāo)操作來訪問數(shù)組下標(biāo)或字典中的鍵所對(duì)應(yīng)的元素星掰。
  • 用字面量語(yǔ)法創(chuàng)建數(shù)組和字典時(shí)多望,若值中又nil,則會(huì)拋出異常氢烘。因此怀偷,務(wù)必確保值里不含 nil。

4.多用類型常量播玖,少用#define預(yù)處理指令

#define ANIMATION_DURATION 0.3

這樣的預(yù)處理容易導(dǎo)致整個(gè)工程里的ANIMATION_DURATION都是0.3椎工,很有可能會(huì)改變系統(tǒng)定義的一些宏。
我們選擇這種方式:

 static const  NSTimerInterval  kAnimationDuration = 0.3;

我們的習(xí)慣是如果常量只是出現(xiàn)在一些類的“編譯單元(translation unit)”之內(nèi)蜀踏,則前面加"k"维蒙,如果是類之外也可見就用類名做前綴。

定義常量的位置很重要果覆,我們總喜歡在頭文件里聲明預(yù)處理指令颅痊,這樣做真的很糟糕,當(dāng)常量名稱有可能互相沖突時(shí)更是如此局待。

如果想要定義一個(gè)定義域在某個(gè)文件中的定義的話斑响,我們可以這樣:
static const NSTimerInterval kAnimationDuration = 0.3;
變量一定要用static和const一同修飾,用const修飾的變量如果遭到更改就會(huì)報(bào)錯(cuò)钳榨,而static修飾符則意味著變量?jī)H在定義此變量的編譯單元中可見舰罚,也就是這個(gè).m中。假如聲明此變量時(shí)不加static薛耻,則編譯器就會(huì)為它創(chuàng)建一個(gè)“外部符號(hào)(external symbol)”营罢。此時(shí)如果是另一個(gè)編譯單元里面也聲明了一樣的變量就會(huì)拋出錯(cuò)誤。

而想要一個(gè)全局變量的話:

//.h:
extern  NSString * const  EOCStringConstant;
//.m:
NSString * const EOCStringConstant  = @“VALUE”;

const修飾符放在EOCStringConstant指針外面就符合語(yǔ)義為防止指針的方向饼齿。
編譯器看到頭文件中的extern關(guān)鍵字饲漾,就會(huì)明白如何在引入磁頭文件的代碼中處理該常量了瘟滨。在全局符號(hào)表將會(huì)有一個(gè)名教EOCStringConstant的符號(hào)。也就說能颁,編譯器無需查看器定義杂瘸,即允許代碼使用此常量。因?yàn)樗阑锞眨?dāng)連接成二進(jìn)制文件之后败玉,肯定能好到這個(gè)常量。
此類常量必須要定義镜硕。而且只能定義一次运翼。編譯器會(huì)在"數(shù)據(jù)段(data section)"為字符串分配存儲(chǔ)空間
。鏈接器會(huì)把目標(biāo)文件和其他目標(biāo)文件相鏈接兴枯,生成最終的二進(jìn)制文件血淌。鏈接器可以隨時(shí)解析這個(gè)常量。

要點(diǎn):
  • 不要用預(yù)處理指令定義常量财剖。這樣定義出來的常量不含類型信息悠夯,編譯器只是會(huì)在編譯之前根據(jù)此執(zhí)行查找與替代操作。即使有人重新定義了常量值躺坟,編譯器也不會(huì)產(chǎn)生警告信息沦补。這將導(dǎo)致應(yīng)用程序中的常量值不一致。
  • 在實(shí)現(xiàn)文件中使用static const來定義“只在編譯單元內(nèi)可見的常量”咪橙。由于次常量不在全局符號(hào)表中夕膀,所以無需在其前面加前綴。
  • 在頭文件中使用extern來聲明全局常量美侦,并在相關(guān)實(shí)現(xiàn)文件中定義其值产舞。這種常量要出現(xiàn)在全局符號(hào)表中,所以其名稱應(yīng)加上區(qū)隔菠剩,通常用與之相關(guān)的類名做前綴.

5.用枚舉表示狀態(tài)易猫,選項(xiàng),狀態(tài)碼

枚舉可以用來列舉某個(gè)對(duì)象的一些形態(tài)

enum EOCConnectionState{
       EOCConnectionStateDisconnected,
       EOCConnectionStateConnecting,
       EOCConnectionStateConnected,
};
如果想要簡(jiǎn)單編寫的話
typedef enum EOCConnectionState = EOCConnectionState;
EOCConnectionState state = /*****/;    //這樣

還有一種情況適用枚舉類型赠叼,定義選項(xiàng)的時(shí)候擦囊,如果其中可以彼此組合那就更加適合了违霞,各個(gè)選項(xiàng)之間可通過"按位或操作符(bitwise)"嘴办。

enum UIViewAutoresizing{
      UIViewAutoresizingNone   = 0,
      UIViewAutoresizingFlexibleLeftMargin = 1<<0,
      UIViewAutoresizingFlexibleWidth = 1<<1,
      UIViewAutoresizingFlexibleRightMargin = 1<<2,
}
這樣除了none,其他的都可以多選买鸽。

下面是”<<“符號(hào)帶來的內(nèi)存存儲(chǔ)方式:


多選項(xiàng)枚舉.png
要點(diǎn):
  • 應(yīng)該用枚舉來表示狀態(tài)機(jī)的狀態(tài)涧郊,傳遞給方法的選項(xiàng)以及狀態(tài)碼等值,給這些值起一個(gè)易懂的名字眼五。
  • 如果把傳遞給某個(gè)方法的選項(xiàng)表示為枚舉類型妆艘,而多個(gè)選項(xiàng)又同時(shí)實(shí)現(xiàn)彤灶,那么就將個(gè)選項(xiàng)值定義為2的冪,以便通過按位或操作將其組合起來批旺。
  • 用NS_ENUM和NS_OPTIONS宏來定義枚舉類型幌陕,并指明其底層數(shù)據(jù)類型。這樣做可以確保枚舉是用開發(fā)者所選的底層數(shù)據(jù)類型實(shí)現(xiàn)出來的汽煮,而不會(huì)采用編譯器所選的類型搏熄。
  • 在處理枚舉類型的switch語(yǔ)句中不要定義default分支,這樣的話暇赤,加入新枚舉之后心例,編譯器就會(huì)提示開發(fā)者:switch語(yǔ)句并未處理所有枚舉。

第二章 對(duì)象鞋囊,消息止后,運(yùn)行期

6.理解“屬性”這一概念

在OC中,對(duì)象(object)就是基本構(gòu)造單位(building block),開發(fā)者可以通過對(duì)象來存儲(chǔ)并傳遞數(shù)據(jù)溜腐。在對(duì)象之間傳遞數(shù)據(jù)并執(zhí)行任務(wù)的過程就叫做“消息傳遞”(Messaging)译株。
當(dāng)應(yīng)用程序運(yùn)行起來以后,為其提供相關(guān)支持的代碼叫做”O(jiān)bjective-C運(yùn)行期環(huán)境(Objective-C runtime)”,它提供了一些使得對(duì)象之間能夠傳遞消息的重要函數(shù)挺益,并且包含創(chuàng)建類實(shí)例所用的全部邏輯古戴。

關(guān)于NSString的Copy和Strong修飾符

這邊重申一下為什么NSString使用copy而不用strong:
因?yàn)槿绻鸑SString對(duì)象是取一個(gè)NSMutableString對(duì)象的數(shù)值的話,當(dāng)NSMutableString的對(duì)象數(shù)值發(fā)生改變的時(shí)候矩肩,NSString會(huì)相應(yīng)的發(fā)生改變现恼,而不是保持遠(yuǎn)數(shù)值.
為什么NSMutableString使用strong而不用copy:
因?yàn)槿绻莄opy修飾的話,NSMutableArray的數(shù)值就不能發(fā)生改變黍檩。

先隨便寫一個(gè)聲明

@interface EOCPerson:NSObject{
 @public
        NSString *  _firstName;
        NSString * _lastName;
@private
        NSString * _ someInternalData
}

如果我們?cè)赺firstName上面在加一個(gè)_iSuName而firstName和lastName都向下移動(dòng)叉袍,這樣EOCPerson就會(huì)擁有三個(gè)屬性,其實(shí)一搭眼好像這樣完全沒有問題刽酱,其實(shí)對(duì)象布局在編譯器布局的時(shí)候已經(jīng)固定了喳逛,所以當(dāng)你在上面加入一個(gè)iSuName的時(shí)候其實(shí),想要獲取firstName的類還是會(huì)根據(jù)編譯器時(shí)候的偏移量來獲取firstName棵里,但是實(shí)際上獲取的卻是iSuName的值润文,原來的元素全部都后移的時(shí)候,那么以偏移量為參考量的做法會(huì)導(dǎo)致請(qǐng)求的元素不兼容(incompatibility)- - 殿怜,OC解決這種問題的方法就是把實(shí)例變量當(dāng)做一種存儲(chǔ)偏移量所用的“特殊變量”典蝌,然后交由“類對(duì)象”保存(14條詳解)。這個(gè)時(shí)候偏移量在運(yùn)行的時(shí)候就會(huì)動(dòng)態(tài)改變头谜,找到正常的數(shù)值骏掀。這就是穩(wěn)固的ABI(Application Binary Interface)。

get 和 set方法:

EOCPerson * aPerson =[Person new];
aPerson.firstName = @“Bob”;  // same as  [aPerson  setFirstName: Bob];
NSString * lastName = aPerson.lastName;   // same as  NSString * lastName = [aPerson lastName];

然而屬性還有更多的優(yōu)勢(shì)。如果使用了屬性的話截驮,編譯器就會(huì)自動(dòng)編寫訪問這些屬性所需的方法笑陈。
如果想要改變名字的長(zhǎng)相的話:

@implementation EOCPerson 
@synthesize firstName = _myFirstName ;
@synthesize lastName = _mySecondName;

這樣你在聲明里面定義的firstName,lastName就變成了_myFirstName,_mySecondName。

當(dāng)然你也可以選擇手動(dòng)生成get,set方法(@dynamic)

@implementation EOCPerson 
@dynamic firstName,lastName 

編譯器不會(huì)為上面這兩個(gè)屬性自動(dòng)合成存儲(chǔ)方法和實(shí)例變量葵袭。如果用代碼訪問其中的屬性涵妥,編譯器也不會(huì)發(fā)出警告消息。

屬性特質(zhì):

  1. 原子性:嚴(yán)格意義上說原子性(automicity)因?yàn)槭褂猛芥i的原因是要比非原子性(nonatomic)要更加安全坡锡,但是在屬性上添加同步鎖要消耗過多的資源妹笆。所以我們基本上會(huì)使用nonatomic做修飾符。
  2. 讀寫權(quán)限:也就是readwrite(讀寫)娜氏,readonly(只讀)拳缠。
  3. 內(nèi)存管理語(yǔ)義:
    assign : 只會(huì)執(zhí)行對(duì)“純量類型”,例如(CGFloat或者是NSInteger等)的簡(jiǎn)單賦值操作。
    strong : 定義了一種“擁有關(guān)系”贸弥,為這種屬性設(shè)置新值時(shí)窟坐,設(shè)置方法先保留新值,并釋放舊值绵疲,然后將新值替換上去哲鸳。
    weak: 定義了一種”非擁有關(guān)系”,為這種屬性設(shè)置新值的時(shí)候,設(shè)置方法即不保留新值也不釋放舊值盔憨。此特質(zhì)同assign類似徙菠,然而在屬性所指的對(duì)象遭到摧毀的時(shí)候,屬性值也會(huì)被清空郁岩。
    unsafe_unretained: 和assign相同婿奔,但是它適用于“對(duì)象類型”,該特質(zhì)表達(dá)一種“非擁有關(guān)系”,但是當(dāng)目標(biāo)對(duì)象遭到摧毀的時(shí)候问慎,屬性值不會(huì)自動(dòng)清空萍摊,這一點(diǎn)和weak有區(qū)別。
    copy: 此特質(zhì)所表示的所屬關(guān)系和strong類似如叼。然而設(shè)置方法并不保留新數(shù)值冰木,而是將其“拷貝”。當(dāng)屬性類型為NSString *時(shí)候笼恰,經(jīng)常用此特質(zhì)來保護(hù)其封裝性踊沸。

需要注意:如果自己來實(shí)現(xiàn)存取方法,那么應(yīng)該保證其具備相關(guān)屬性所聲明的特質(zhì)社证,比方說逼龟,如果將某個(gè)屬性聲明為copy,那么就應(yīng)該在”設(shè)置方法”中拷貝相關(guān)對(duì)象。否則會(huì)誤導(dǎo)該屬性的使用者猴仑。
說道這個(gè)在初始化的時(shí)候?yàn)槭裁床皇怯胹et方法來保證每次都調(diào)用NSString都能帶上Copy的內(nèi)存語(yǔ)義审轮。而是使用屬性賦值給copy的:_firstName = [firstName copy];(第七條詳解)

//.h
@interface EOCPerson : NSManagerObject
@property (copy) NSString * firstName ;
@property (copy) NSString * lastName ;

- (id) initWithFirstName: (NSString *)firstName lastName:(NSString *)secondName;
@end
//.m
- (id) initWithFirstName: (NSString *)firstName lastName:(NSString *)secondName{

   if( self = [super init]){
            _firstName = [firstName  copy];
            _lastName = [lastName copy];
    }
}
要點(diǎn):
  • 可以用@property語(yǔ)法來定義對(duì)象中所封存的數(shù)據(jù)。
  • 通過“特質(zhì)”來制定存儲(chǔ)數(shù)據(jù)所需的正確定義辽俗。
  • 在設(shè)置屬性所對(duì)象的實(shí)力變量時(shí)疾渣,一定要遵從屬性所聲明的寓意。
  • 開發(fā)iOS程序時(shí)候應(yīng)該使用nonatomic屬性崖飘,因?yàn)閍tomic屬性會(huì)嚴(yán)重影響性能榴捡。

7.在對(duì)象內(nèi)部盡量直接訪問實(shí)例變量

首先確認(rèn)一下 _name:直接訪問對(duì)象實(shí)例變量 self.name 間接訪問實(shí)例變量

self.name 和_name 的區(qū)別是:

  1. 由于不經(jīng)過Objective-C的“方法派發(fā)”步驟,所以直接訪問實(shí)例變量的速度當(dāng)然比較快朱浴。在這種情況下所生成的代碼會(huì)直接訪問保存對(duì)象變量的那塊內(nèi)存吊圾。
  2. 直接訪問實(shí)例變量時(shí),不會(huì)調(diào)用其“設(shè)置方法”翰蠢,這就繞過了相關(guān)屬性定義的“內(nèi)存管理語(yǔ)義”项乒。比方說,如果在ARC下直接訪問一個(gè)聲明為copy的屬性梁沧,那么并不會(huì)拷貝該屬性檀何,只會(huì)保留新值并釋放舊值。
  3. 如果直接訪問實(shí)例變量廷支,那么不會(huì)觸發(fā)“鍵值觀察(KVO)”频鉴,通知。這樣做是否會(huì)產(chǎn)生問題恋拍,還取決于具體的對(duì)象行為垛孔。

回想一下上一個(gè)Tip上的問題為什么在初始化的時(shí)候不使用self.firstName = firstName,而選擇_firstName = [firstName copy];
嗨呀施敢,好氣啊周荐,我思想跑的太遠(yuǎn)了,我以為是在子類重寫父類屬性會(huì)影響父類的運(yùn)行呢僵娃,并不是啊只是子類運(yùn)行的時(shí)候如果發(fā)現(xiàn)父類的init方法中使用點(diǎn)語(yǔ)法而子類中正好重寫了這個(gè)方法羡藐,那么就會(huì)走子類的方法,子類如果有限制條件語(yǔ)句的時(shí)候會(huì)導(dǎo)致條件在不清楚的情況下意外不能通過悯许。而如果直接是屬性的話就不會(huì)走set方法直接就跳過子類的判斷環(huán)節(jié)了仆嗦。例子請(qǐng)看EOCPerson.demo

還有一種情況下使用獲取方法
那就是惰性初始化(lazy initialization)

- (EOCBrain *)brain{
    if(!_brain){
         _brain = [Brain  new];
   }
        return _brain;
}

這種情況下你不self.brain的話代碼就沒法走了。

要點(diǎn):
  • 在對(duì)象內(nèi)部讀取數(shù)據(jù)時(shí)先壕,應(yīng)該直接通過實(shí)例變量來讀瘩扼,而寫入數(shù)據(jù)時(shí),則通過屬性來寫垃僚。
  • 在初始化方法及dealloc方法中集绰,總是應(yīng)該直接通過實(shí)例變量來讀取數(shù)據(jù)。
  • 有時(shí)會(huì)使用惰性初始化計(jì)數(shù)配置某分?jǐn)?shù)據(jù)谆棺,在這種情況下栽燕,需要通過屬性來讀取數(shù)據(jù)罕袋。

8.理解“對(duì)象等同性”這一概念

按照 “==” 操作符比較出來的結(jié)果未必是我們想要的,因?yàn)樵摬僮鞅容^的是兩個(gè)指針本身碍岔,而不是期所指的對(duì)象浴讯。

 NSString * foo = @“Badger  123”;
 NSString * bar = [NSStringWithFormat:@“Badger  %i”,123];
 BOOL  equalA = ( foo == bar);
 BOOL  equalB = [foo isEqual:bar] ;
// set out -> equalA == NO
  1. 第一種判定的方法就是: isEqual
    所謂的所有屬性判斷:
    a.判斷兩個(gè)指針是否相等,當(dāng)切僅當(dāng)指針值相等的時(shí)候才會(huì)想等蔼啦。
    b.比較兩個(gè)對(duì)象所屬的類榆纽。
    c.逐條屬性判斷。
  2. 第二種判定的方法就是:hash
    若兩個(gè)對(duì)象相等捏肢,則其哈希嗎也相等奈籽。但是兩個(gè)哈希嗎相等的對(duì)象未必相等。
    假如某個(gè)collection是用set實(shí)現(xiàn)的鸵赫,那么set可能會(huì)根據(jù)哈希嗎把對(duì)象分裝到不同的數(shù)組中衣屏。向set中添加新對(duì)象時(shí),要根據(jù)其哈希嗎找到與之相關(guān)的那個(gè)數(shù)組辩棒。依次檢查其中各個(gè)元素勾拉。看是哦否有相同的盗温,如果有相同的藕赞,那就說明要添加的對(duì)象已經(jīng)在set里面了。由此可知卖局,如果令每個(gè)對(duì)象返回相同的哈希嗎斧蜕,那么在set中已經(jīng)有10000000個(gè)對(duì)象的情況下,如要繼續(xù)向里面添加對(duì)象砚偶,就需要全部便利一邊批销。

那么有沒有可能讓set 中含有兩個(gè)相同的對(duì)象呢。
下面代碼:

    NSMutableSet * set = [NSMutableSet new];
    NSMutableArray * arrayA =[@[@1,@2] mutableCopy];
    [set addObject:arrayA];
    NSLog(@"%@",set);
    
    NSMutableArray * arrayB =[@[@1,@2] mutableCopy];
    [set addObject:arrayB];
    NSLog(@"%@",set);
    
    NSMutableArray * arrayC =[@[@1] mutableCopy];
    [set addObject:arrayC];
    NSLog(@"%@",set);
    
    [arrayC addObject:@2];
    NSLog(@“%@",set);

最后在NSSet * setB =[set Copy];
就成了含有兩個(gè) (1染坯,2)….
要點(diǎn):
  • 若想檢測(cè)對(duì)象的等同性均芽,請(qǐng)?zhí)峁眎sEqual”和hash方法。
  • 相同的對(duì)象必須具備相同的哈希嗎单鹿,但是兩個(gè)哈希碼相同的對(duì)象確未必相同
  • 不要盲目的逐個(gè)檢測(cè)每條對(duì)象掀宋,而是應(yīng)該依照具體需求制定檢測(cè)方案。
  • 編寫hash方法時(shí)仲锄,應(yīng)該使用計(jì)算速度快而且哈希碼碰撞幾率低的算嗎

9.以“類族模式”隱藏實(shí)現(xiàn)細(xì)節(jié)

“類族”是一種很有用的模式(pattern)劲妙,可以隱藏“抽象基類”背后的實(shí)現(xiàn)細(xì)節(jié)。Objective-C系統(tǒng)框架中普遍使用此模式儒喊。比如:

  + (UIButton *)buttonWithType:(UIButtonType)type;

該方法的返回對(duì)象镣奋,其類型取決于按鈕的類型。然而怀愧,不管返回是什么類型的對(duì)象侨颈,它們都集成來自同一個(gè)基類:UIButton余赢。這么做的意義在于:UIButton類的使用者無需關(guān)心創(chuàng)建出來的按鈕屬于哪一個(gè)子類,也不用考慮繪制細(xì)節(jié)哈垢。
在測(cè)試代碼里面有創(chuàng)建類族的代碼

在這些代碼里面妻柒,基類實(shí)現(xiàn)了一個(gè)“類方法”,該方法根據(jù)待創(chuàng)建的雇員類別分配好對(duì)應(yīng)的雇員類實(shí)力温赔。這種“工廠模式(Factory pattern)”是創(chuàng)建類族的辦法之一蛤奢。

如果對(duì)象所屬的類位于某個(gè)類族中鬼癣,那么在查詢其類型消息(introspection)時(shí)就要小心了陶贼,你可能覺得創(chuàng)建了某個(gè)類的實(shí)例,然而實(shí)際上創(chuàng)建的卻是其子類的實(shí)例待秃。在這個(gè)Employee這個(gè)例子中拜秧,[employee isMemberOfClass:[EOCEmployee class]]似乎會(huì)返回YES,但實(shí)際上返回的卻是NO,因?yàn)閑mployee并非Empoyee類的實(shí)例,而是其某個(gè)子類的實(shí)例章郁。

Cocoa里的類族

系統(tǒng)框架中又許多類族枉氮。大部分collection類都是類族,例如NSArray與其可變的版本NSMutableArray暖庄。這樣看來聊替,實(shí)際上有兩個(gè)抽象基類,一個(gè)用于不可變數(shù)組培廓,另一個(gè)用于可變數(shù)組惹悄。盡管具備公共接口的類有兩個(gè),但仍然可以合起來算作一個(gè)類族肩钠。
像NSArray這樣的類的背后其實(shí)是個(gè)類族(對(duì)于大部分collection類而言都是這樣)泣港,明白這一點(diǎn)很重要,否則可能會(huì)寫出下面這種代碼

id maybeArray = /****/
if  ([maybeAnArray  class] == [NSArray  class]) {
                 // Will  never  be hit  永遠(yuǎn)不會(huì)進(jìn)入
}

你要是知道NSArray是個(gè)類族价匠,那就會(huì)明白上述代碼錯(cuò)在哪里:[maybeAnArray class]所返回的類絕不可能是NSArray類本身当纱,因?yàn)橛蒒SArray的初始化方法所返回的那個(gè)實(shí)例其類型是隱藏在類族公共接口后面的某個(gè)內(nèi)部類型。我們可以通過
判斷對(duì)象是否在類族iskindof來判斷(14條詳細(xì)講解):

id maybeAnArray = /*****/
if ([maybeAnArray  isKindOfClass:[NSArray class]]){
    
         // Will be hit     // 會(huì)走這邊
}
要點(diǎn):
  • 類族模式可以把實(shí)現(xiàn)細(xì)節(jié)隱藏在一套簡(jiǎn)單的公共接口后面踩窖。
  • 系統(tǒng)框架中經(jīng)常使用類族
  • 從類族的公共抽象基類中繼承子類時(shí)要當(dāng)心坡氯,若有開發(fā)文檔請(qǐng)先閱讀。

10.在既有類中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)

有時(shí)需要在對(duì)象中存放相關(guān)信息洋腮。我們可以使用關(guān)聯(lián)對(duì)象Associated Object廉沮。
可以給某對(duì)象關(guān)聯(lián)許多其他對(duì)象,這些對(duì)象通過“鍵”來區(qū)分徐矩。存儲(chǔ)對(duì)象值的時(shí)候滞时,可以指明”存儲(chǔ)策略(storage policy)“,用以維護(hù)響應(yīng)的“內(nèi)存管理語(yǔ)義”滤灯。

OBJC_ASSOCIATION_ASSIGN   assign 
OBJC_ASSOCIATION_COPY      copy

下列方法可以管理關(guān)聯(lián)對(duì)象:

void objc_setAssociatedObject (id object ,void * key ,id value,objc_AssociationPolicy policy)

此方法以給定的鍵和策略為某對(duì)象設(shè)置關(guān)聯(lián)對(duì)象值坪稽。

void objc_getAssociatedObject(id object, void object)

此方法根據(jù)給定的鍵從某對(duì)象中獲取相應(yīng)的關(guān)聯(lián)對(duì)象值曼玩。

void objc_removeAssocatedObjects(id object)

此方法移除指定對(duì)象的全部關(guān)聯(lián)對(duì)象。
做的方法:

- (void)askUserAQuestion{

    UIAlertView * alert =[[UIAlertView alloc]initWithTitle:@"Question" message:@"What do you want to do?" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Continue", nil];
    
    void(^block)(NSInteger) = ^(NSInteger buttonIndex){
    if (buttonIndex == 0){
                 [self doCancle];
          }else{
                 [self doContinue];
          }
    };
    objc_setAssociatedObject(alert, EOCMyAlertViewKey, block, OBJC_ASSOCIATION_COPY);
    [alert show];
}

//UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{

    void (^block)(NSInteger)= objc_getAssociatedObject(alertView, EOCMyAlertViewKey);
    block(buttonIndex);
}
要點(diǎn):
  • 可以通過“關(guān)聯(lián)對(duì)象”機(jī)制來把兩個(gè)對(duì)象連起來
  • 定義關(guān)聯(lián)對(duì)象時(shí)可指定內(nèi)存管理語(yǔ)義窒百,用以訪問定義屬性時(shí)采用的“擁有關(guān)系”和“非擁有關(guān)系”
  • 只有在其他做法不可行的時(shí)候才會(huì)應(yīng)用關(guān)聯(lián)對(duì)象黍判,因?yàn)檫@種做法通常會(huì)引入難于查找的bug。

11.理解 objc_msgSend的作用

objective-C的術(shù)語(yǔ)來說篙梢,這叫“傳遞消息”

id returnValue = [someObject messageName: parameter];

在本例中顷帖,someObject叫做接受者(receiver),messageName叫做“選擇子(selector)”渤滞。

void objc_msgSend(id self,SEL cmd,…)
id returnValue =objc_msgSend (someObject,@selector(messageName:),parameter);

為了完成調(diào)用的做法贬墩,該方法需要在接受者所屬的類中搜索器“方法列表”(list of methods),如果能找到與選擇子名稱相符的方法妄呕,就跳至其實(shí)現(xiàn)代碼陶舞,如果找不到就沿著繼承體系向上查找,等找到合適的方法之后在跳轉(zhuǎn)绪励。如果最終還是找不到相符的方法肿孵,那就執(zhí)行“消息轉(zhuǎn)發(fā)(message forwording)操作”。

這么看來疏魏,調(diào)用一個(gè)方法好像需要很多步驟停做。索性objc_megSend會(huì)將匹配結(jié)果緩存在“快速映射表(fash map)里面”。每一個(gè)類都有這樣的一塊緩存大莫,當(dāng)然“快速請(qǐng)求路徑”還是不如靜態(tài)來的快蛉腌,但是也不會(huì)慢很多。

objc_msgSend_struct;
objc_msgSend_fpret;
objc_msgSendSuper;

方法存儲(chǔ)的方式大概是:

<return_type> Class_selector(id self ,SEL_cmd,…)

真正的函數(shù)其實(shí)和這個(gè)差不多葵硕,因?yàn)?code>“尾調(diào)用優(yōu)化 (tail- call optimization)技術(shù)”:
如果某個(gè)函數(shù)的最后一項(xiàng)操作是調(diào)用某個(gè)函數(shù)的話眉抬,就會(huì)調(diào)用“尾調(diào)用優(yōu)化”技術(shù)。編譯器會(huì)生成調(diào)轉(zhuǎn)至另一個(gè)函數(shù)所需的指令碼懈凹,而且不會(huì)調(diào)用堆棧中推入新的“棧幀(frame stack)”蜀变。只有當(dāng)某函數(shù)的最后一個(gè)操作僅僅是調(diào)用其他函數(shù)而不會(huì)將其返回值另作他用的時(shí)候次啊會(huì)執(zhí)行”尾調(diào)用優(yōu)化”,這樣做可以防止過做的發(fā)生“棧溢出”現(xiàn)象介评。

要點(diǎn):
  • 消息由接受者库北,選擇子及參數(shù)構(gòu)成。給某對(duì)象“發(fā)送消息(invoke a message)”也就相當(dāng)于在該對(duì)象上“調(diào)用方法(call a method)”们陆。
  • 發(fā)給某對(duì)象的全部消息都要由“動(dòng)態(tài)消息派發(fā)系統(tǒng)(dynamic message dispatch system)”來處理寒瓦,該系統(tǒng)會(huì)查出對(duì)應(yīng)的方法,執(zhí)行其代碼坪仇。

12.理解消息轉(zhuǎn)發(fā)機(jī)制

若想令類能理解某條消息杂腰,我們必須以程序碼實(shí)現(xiàn)出對(duì)應(yīng)的方法才行。但是椅文,在編譯期向類發(fā)送了無法解讀的消息并不會(huì)報(bào)錯(cuò)喂很,因?yàn)樵谶\(yùn)行期可以繼續(xù)給類中添加方法也就是“消息轉(zhuǎn)發(fā)(message forwarding)”機(jī)制惜颇,程序員可經(jīng)由此過程告訴對(duì)象應(yīng)該如何處理位置消息。

消息轉(zhuǎn)發(fā)機(jī)制報(bào)錯(cuò)提示.png

關(guān)于錯(cuò)誤初始化的報(bào)錯(cuò):
此異常表明:消息接受者的類型是__NSCFNumber少辣,而該接受者無法理解名為lowercaseString的選擇子凌摄。

消息轉(zhuǎn)發(fā)分為兩大階段:

  1. 先征詢接受者,所屬的類漓帅,看其是否能動(dòng)態(tài)添加方法锨亏,以處理這個(gè)“位置的選擇子(unknown selector)”,這叫做“動(dòng)態(tài)方法解析(dynamic method resolution)”忙干。
  2. 就是完整的消息轉(zhuǎn)發(fā)機(jī)制(full forwarding mechanism)器予。 如果運(yùn)行期喜用已經(jīng)吧第一階段執(zhí)行完了,那么接受者自己就無法再以動(dòng)態(tài)新增方法的手段來響應(yīng)包含該選擇自的消息豪直,此時(shí)劣摇,運(yùn)行期系統(tǒng)會(huì)請(qǐng)求接受者以其他手段來處理與消息相關(guān)的方法調(diào)用珠移。這里面要分成兩個(gè)部分弓乙,首先,請(qǐng)接受者看看有沒有其他對(duì)象能處理這條消息钧惧。若有暇韧,則運(yùn)行期系統(tǒng)會(huì)把消息轉(zhuǎn)給那個(gè)對(duì)象,于是消息轉(zhuǎn)發(fā)結(jié)束浓瞪,若沒有“備用的接受者”懈玻,則啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制,運(yùn)行期喜用會(huì)把與系統(tǒng)有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對(duì)象中乾颁,再給接受者最后一次機(jī)會(huì)涂乌,令其設(shè)法解決當(dāng)前還未處理的這條消息。

主要的轉(zhuǎn)發(fā)路徑為:
resolveInstanceMethod ----> forwardingTargetForSelector ----> forwardInvocation

要點(diǎn):
  • 若對(duì)象無法響應(yīng)某個(gè)選擇子英岭,則進(jìn)入消息轉(zhuǎn)發(fā)流程湾盒。
  • 通過運(yùn)行時(shí)的動(dòng)態(tài)方法解析功能,我們可以在需要用到某個(gè)方法時(shí)再將其加入類中诅妹。
  • 對(duì)象可以把無法解讀的某些選擇子轉(zhuǎn)交給其他對(duì)象來處理罚勾。
  • 經(jīng)過上述兩步之后,如果還是沒有辦法處理選擇子吭狡,那就啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制尖殃。

13.用“方法調(diào)配技術(shù)”調(diào)試“黑盒方法”

類的方法列表會(huì)把選擇子的名字映射到相關(guān)的方法實(shí)現(xiàn)上,使得“動(dòng)態(tài)消息派發(fā)系統(tǒng)”能夠依據(jù)此找到應(yīng)該調(diào)用的方法划煮。這些方法均以函數(shù)指針的形式來表示送丰,這種指針叫做IMP,其原型為:

id (*IMP) (id, SEL ,…)

獲得想要交換的兩個(gè)函數(shù)的方法:

Method class_getInstanceMethod(Class aClass,SEL aSelector)

交換的方法:

void method_exchangeImplementations(Method m1, Method m2)

Method originalMethod = class_getInstanceMethod(NSStringClass,@selector(lowercaseString));

Method swappedMethod =
class_getInstanceMethod(NSStringClass,@selector(uppercaseString));

method_exchangeImpLementations(originalMethod , swappedMethod);

實(shí)際應(yīng)用其實(shí)很少交換,我們都是要添加一個(gè)方法弛秋。

要點(diǎn):
  • 在運(yùn)行期器躏,可以向類中新增或替代選擇子所對(duì)應(yīng)的方法實(shí)現(xiàn)牵现。
  • 使用另一份實(shí)現(xiàn)來替代原有的方法實(shí)現(xiàn),這道工序叫做“方法調(diào)配”,開發(fā)者常用此技術(shù)向原有實(shí)現(xiàn)添加新功能爸邢。
  • 一般來說魄鸦,只要調(diào)試程序的時(shí)候次啊會(huì)需要在運(yùn)行期修改方法實(shí)現(xiàn),這種做法不適合濫用贼急。

14.理解“類對(duì)象”的用意

每一個(gè)Objective-C 對(duì)象實(shí)例都是指向某塊內(nèi)存地址的指針。所以在聲明變量時(shí)捏萍,類型后要跟上一個(gè) * 字符:

NSString * pointerVariable = @“some String”;

描述OC對(duì)象所用的數(shù)據(jù)結(jié)構(gòu)定義在運(yùn)行期程序庫(kù)的頭文件中太抓,id類型本身也在定義在這里:

typedef struct objc_object{
      Class isa;
} * id ;

typedef struct objc_class * Class;
struct objc_class{
           Class  isa;
           Class super_Class;
           const char * name;
           long version;
           long info ;
           long instance_size;
           struct objc_ivar_list * ivars;
           struct objc_method_list ** methodLists;
           struct  objc_cache * cache;
           struct   objc_protocol_list * protocols;
}

在類集成體系中查詢類型信息,注意下這這個(gè)書里面呢mutableDic調(diào)用isMemberofClass對(duì)比NSMutableDic是返回YES但是我做測(cè)試寫代碼的時(shí)候發(fā)現(xiàn)并不是這樣的,返回的還是NO令杈。

NSMutableDictionary  * dict =[NSMutableDictionary  new];
[dict  isMemberofClass: [NSDictionary  class]];          NO
[dict  isMemberofClass:[NSMutbaleDictionary  class]];           NO
[dict  isKindofClass:[NSDictionary class]];               YES
[dict  isKindofClass:[NSArray  class]];           NO
要點(diǎn):
  • 每個(gè)實(shí)例都有一個(gè)指向Class對(duì)象的指針走敌,用以表明類型,而這些Class對(duì)象則構(gòu)成類的集成體系逗噩。
  • 如果對(duì)象類型無法在編譯器確定掉丽,那么就應(yīng)該使用類型消息查詢方法來探知。
  • 盡量使用類型消息查詢方法來確定對(duì)象類型异雁,而不要直接比較類對(duì)象捶障,因?yàn)槟承?duì)象可能實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)功能。

第三章 接口和api設(shè)計(jì)

15.用前綴避免命名空間沖突

要點(diǎn):
  • 選擇與你的公司纲刀,應(yīng)用程序或二者都關(guān)聯(lián)的名字做類名的前綴项炼,并在所有代碼中軍使用這個(gè)前綴
  • 若自己所開發(fā)的程序中使用了第三方庫(kù),則應(yīng)為其中的名稱加上前綴示绊。

16.提供“全能初始化方法”

要點(diǎn):
  • 在類中提供一個(gè)全能初始化方法锭部,并在文檔中指明。其他初始化方法均應(yīng)調(diào)用此方法面褐。
  • 若全能初始化方法和超類不同拌禾,則需覆寫超類中對(duì)應(yīng)的方法。
  • 如果超類的初始化方法不適合子類盆耽,那么應(yīng)該覆寫這個(gè)超類方法蹋砚,并拋出異常。

17.實(shí)現(xiàn)description方法

平時(shí)的時(shí)候想要看看打印的效果的時(shí)候我們通常使用NSLog摄杂,但是使用MVVM的時(shí)候傳遞的對(duì)象基本上都是以自定義model類型來傳遞的坝咐,那么直接log可能就會(huì)出現(xiàn)這樣的情況:

object = <EOCPerson:0x7fd9a1600600>

顯然model內(nèi)部的成員變量就別想著看了。解決的辦法很簡(jiǎn)單析恢,在類中加入description方法:

- (NSString *)description{
     return [NSString StringWithFormat:@<%@:%p,\%@ %@\>,[self class],self,_firstName,_lastName];
}
//這樣打印出來的數(shù)據(jù)
<EOCPerson:0x7f249c030f0,"Bob Smith">
要點(diǎn):
  • 實(shí)現(xiàn)description方法返回一個(gè)有意義的字符串墨坚,用以描述該實(shí)例。
  • 若想在調(diào)試的時(shí)候打印出詳盡的對(duì)象描述信息,則應(yīng)實(shí)現(xiàn)debugDescription泽篮。

18.盡量使用不可變對(duì)象

如果那可變對(duì)象放入容器(collection)之后再修改其內(nèi)容盗尸,那么很容易就會(huì)破壞set的內(nèi)部數(shù)據(jù)結(jié)構(gòu),使其失去固有的語(yǔ)義帽撑。因此泼各,筆者建議大家盡量減少對(duì)象中的可變內(nèi)容。

有時(shí)候可能想修改封裝在對(duì)象內(nèi)部的數(shù)據(jù)亏拉,但是卻不想令這些數(shù)據(jù)為外人所改動(dòng)扣蜻。這種情況下,通常做法是在對(duì)象內(nèi)部將readonly屬性重新聲明為readwrite及塘。
也就是在.h使用readOnly 在.m readwrite莽使。

要點(diǎn):
  • 盡量創(chuàng)建不可變的對(duì)象
  • 若某屬性僅可用于對(duì)象內(nèi)部修改,則在“class-continuation分類”中將其由readonly屬性擴(kuò)展為readwrite屬性笙僚。
  • 不要把可變的collection(容器)作為屬性公開芳肌,而應(yīng)提供相應(yīng)方法,以此修改對(duì)象中的可變?nèi)萜鳌?/li>

19.使用清晰而協(xié)調(diào)的命名方式

要點(diǎn):
  • 起名時(shí)應(yīng)遵守標(biāo)準(zhǔn)的 Objective-C命名規(guī)范肋层,這樣創(chuàng)建出來的接口更容易為開發(fā)者所理解亿笤。
  • 方法名要言簡(jiǎn)意賅,從左到右讀起來要像個(gè)日常用語(yǔ)中的句子才好槽驶。
  • 方法明理不要使用縮略后的類型名稱
  • 給方法起名時(shí)的第一要?jiǎng)?wù)就是確保其風(fēng)格與你自己代碼所要集成的框架相符责嚷。

20.為私有方法名加前綴

要點(diǎn):
  • 給私有方法的名稱加上前綴鸳兽,這樣可以很容易地將其同公共方法區(qū)別開掂铐。
  • 不要單用一個(gè)下劃線做私有方法的前綴,因?yàn)檫@種做法是預(yù)留給蘋果公司用的揍异。

21.理解Objective-C 錯(cuò)誤模型

在不是致命錯(cuò)誤(fatal error)的情況下全陨,我們是不會(huì)讓程序直接拋出異常的,比如創(chuàng)建某個(gè)類的時(shí)候衷掷,Coder初始化沒有給出一個(gè)必須要的參數(shù)的時(shí)候辱姨,我們選擇給這個(gè)創(chuàng)建對(duì)象返回nil來使得Coder意識(shí)到創(chuàng)作對(duì)象的時(shí)候出現(xiàn)了錯(cuò)誤。

要點(diǎn):
  • 只要發(fā)生了可使整個(gè)應(yīng)用程序崩潰的嚴(yán)重錯(cuò)誤時(shí)戚嗅,才應(yīng)使用異常雨涛。
  • 在錯(cuò)誤不那么嚴(yán)重的情況下,可以指派“委托方法(delegate method)”來處理錯(cuò)誤懦胞,也可以把錯(cuò)誤消息放在NSError對(duì)象里替久,經(jīng)由“輸出參數(shù)”返回給調(diào)用者。

22.理解NSCopying協(xié)議

使用對(duì)象時(shí)經(jīng)常需要拷貝它躏尉,在Objetive-C中蚯根,此操作通常通過copy完成。如果想讓自己的類支持拷貝操作胀糜,那就實(shí)現(xiàn)NSCopying協(xié)議的- copyWithZone:方法

- (id)copyWithZone:(NSZone *)zone{

    Twentytwo * copy =[[self class] allocWithZone:zone];
    return copy;
}

在官方的例子里面颅拦,提到了我們class-continuation里面包含一個(gè)實(shí)例變量的時(shí)候蒂誉,我們?cè)趺礃臃乐箖?nèi)存管理語(yǔ)義導(dǎo)致的原對(duì)象copy之后生成的新對(duì)象copy2繼承這個(gè)內(nèi)部實(shí)例變量的內(nèi)容。所以在做其他屬性copy的時(shí)候?qū)τ谶@個(gè)不想要繼承的內(nèi)部成員變量我們需要mutableCopy距帅。如同例子Twentytwo的例子一樣右锨。

[NSMutableArray  copy]  =>  NSArray
[NSArray mutableCopy]  =>  NSMutableArray

這邊順便說一下為什么不可變的NSArray,NSArray,NSDictionary使用Copy。而可變的NSMutableString,NSMutableArray,NSDictionary使用MutableCopy碌秸。
因?yàn)橛锌赡躈SString獲取的方式是通過mutableStr賦值的陡蝇。為了防止當(dāng)mutableStr更改的時(shí)候str在不知情的情況下更改。而NSMutableString如果是Copy來修飾的哮肚,那么這個(gè)容器就將失去可變的特性登夫,而被Copy成一個(gè)不可變的字符串,數(shù)組或者是字典允趟。

這里面帶一下 ->這個(gè)東西是干嘛的恼策,我的理解是當(dāng)一個(gè)內(nèi)部的實(shí)例變量需要被方法實(shí)現(xiàn)內(nèi)部調(diào)用的時(shí)候就可以使用 copy->_friends這樣。具體可以去看我的例子TwntytwoTest潮剪。

關(guān)于深拷貝(deep copy)和淺拷貝(shallow copy)
淺拷貝:只拷貝容器對(duì)象本身涣楷,而不復(fù)制其中數(shù)據(jù)。
深拷貝: 在拷貝對(duì)象自身時(shí)抗碰,將其底層數(shù)據(jù)也一并復(fù)制過去狮斗。
比如對(duì)NSSet對(duì)象的深拷貝:- initWithSet: copyItems:, 如果items 設(shè)置為YES弧蝇,就是深拷貝碳褒。

要點(diǎn):
  • 若想令自己所寫的對(duì)象具備拷貝對(duì)象,則需實(shí)現(xiàn)NSCopying協(xié)議看疗。
  • 如果自定義的對(duì)象分為可變版本與不可變版本沙峻,那么就要同時(shí)實(shí)現(xiàn)NSCopying和NSMutableCopying協(xié)議。
  • 復(fù)制對(duì)象時(shí)需要決定采用深拷貝還是淺拷貝两芳,一般情況下應(yīng)該盡量執(zhí)行淺拷貝摔寨。
  • 如果你所寫的對(duì)象需要深拷貝,那么可考慮新增一個(gè)專門執(zhí)行深拷貝的方法怖辆。

結(jié)尾

自己寫的筆記首先是用Pages寫的是复,寫完之后放到簡(jiǎn)書里面以為也就剩下個(gè)排版了,結(jié)果發(fā)現(xiàn)基本上每一個(gè)點(diǎn)的總結(jié)都不讓自己滿意竖螃,但是又想早點(diǎn)放上去淑廊,總感覺自己被什么追趕著,哈哈斑鼻,本來寫完筆記的時(shí)候是2W字的蒋纬,結(jié)果到第二次發(fā)表的時(shí)候發(fā)現(xiàn)就成了2.5W了,需要改進(jìn)的東西還是太多,希望朋友們有什么改進(jìn)的提議都可以告訴我蜀备,我會(huì)一直補(bǔ)充這個(gè)筆記,然后抓緊改GitHub上的代碼~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末关摇,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碾阁,更是在濱河造成了極大的恐慌输虱,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件脂凶,死亡現(xiàn)場(chǎng)離奇詭異宪睹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蚕钦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門亭病,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嘶居,你說我怎么就攤上這事罪帖。” “怎么了邮屁?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵整袁,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我佑吝,道長(zhǎng)坐昙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任芋忿,我火速辦了婚禮炸客,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘盗飒。我一直安慰自己嚷量,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布逆趣。 她就那樣靜靜地躺著,像睡著了一般嗜历。 火紅的嫁衣襯著肌膚如雪宣渗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天梨州,我揣著相機(jī)與錄音痕囱,去河邊找鬼。 笑死暴匠,一個(gè)胖子當(dāng)著我的面吹牛鞍恢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼帮掉,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼弦悉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蟆炊,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤稽莉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后涩搓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體污秆,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年昧甘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了良拼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡充边,死狀恐怖将饺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情痛黎,我是刑警寧澤予弧,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站湖饱,受9級(jí)特大地震影響掖蛤,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜井厌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一蚓庭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧仅仆,春花似錦器赞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至咳榜,卻和暖如春夏醉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涌韩。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工畔柔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人臣樱。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓靶擦,卻偏偏與公主長(zhǎng)得像腮考,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子玄捕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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