第一條 了解Objective-C預(yù)言起源
起源:Smalltalk
類(lèi)型:使用消息結(jié)構(gòu)的語(yǔ)言
區(qū)別:使用消息結(jié)構(gòu)的語(yǔ)言,其運(yùn)行時(shí)所應(yīng)執(zhí)行的代碼由運(yùn)行環(huán)境來(lái)決定;而使用函數(shù)調(diào)用的語(yǔ)言則由編譯器決定。
對(duì)象分配在堆空間上,指針?lè)峙湓跅?臻g上
結(jié)構(gòu)體分配在??臻g(CGRect)
第二條 在類(lèi)的頭文件中盡量少引入其他頭文件
用OC編寫(xiě)任何類(lèi)幾乎都需要引入Foundation.h。
向前聲明:@class EOCEmployer;
使用#import而非#include互相引用時(shí)不會(huì)死循環(huán)
要點(diǎn):
1、除非有必要,不要引入頭文件。應(yīng)在類(lèi)的頭文件中使用向前聲明來(lái)提及別的類(lèi),在實(shí)現(xiàn)文件中引入那個(gè)類(lèi)的頭文件??梢员M量降低類(lèi)之間的耦合。
2、無(wú)法使用向前聲明,如聲明某個(gè)類(lèi)遵循一項(xiàng)協(xié)議。盡量把“遵循某協(xié)議”的聲明移到“class-continuation分類(lèi)”中。若不行,就把協(xié)議單獨(dú)放在一個(gè)頭文件中,再引入。
第三條 多用字面量語(yǔ)法,少用與之等價(jià)的方法
字面量語(yǔ)法只是一種“語(yǔ)法糖”
疑問(wèn):mrc下字面量創(chuàng)建的對(duì)象到底有沒(méi)有reatain+1
使用字面量語(yǔ)法創(chuàng)建出來(lái)的字符串、數(shù)組、字典對(duì)象都是不可變的,若想要可變版本的對(duì)象,需要復(fù)制一份
要點(diǎn):
1、使用字面量語(yǔ)法來(lái)創(chuàng)建字符串、數(shù)值、數(shù)組、字典。
2、通過(guò)取下標(biāo)操作來(lái)訪(fǎng)問(wèn)數(shù)組下標(biāo)或字典中的鍵所對(duì)應(yīng)的元素
3、用字面量語(yǔ)法創(chuàng)建數(shù)組或字典時(shí),若值中有nil,則會(huì)拋出異常。因此,務(wù)必確保值里不含nil
第四條 多用類(lèi)型常量,少用#define 預(yù)處理指令
#define ANIMATION_DURATION 0.3
static const NSTimeInterval kAnimationDuration = 0.3;
若不打算公開(kāi)某個(gè)常量,則應(yīng)將其定義在使用該常量的實(shí)現(xiàn)文件里。static修飾符意味著該變量?jī)H在定義此變量的編譯單元可見(jiàn)。在OC語(yǔ)境下,“編譯單元”指每個(gè)類(lèi)的實(shí)現(xiàn)文件(以.m為后綴名)。假如聲明此變量時(shí)不加static,則編譯器會(huì)為它創(chuàng)建一個(gè)“外部符號(hào)”。此時(shí)若是另一個(gè)編譯單元中也聲明了同名變量,那么編譯器就會(huì)拋出錯(cuò)誤。
如果一個(gè)變量既聲明為static,又聲明為const,編譯器根本不會(huì)創(chuàng)建符號(hào),而是會(huì)像#define一樣,把所遇到的變量都替換成常值。
常量放在“全局符號(hào)表中”:
//In the header file
extern NSString *const EOCStringConstant;
//In the implementation file
NSString *const EOCStringConstant = @"VALUE";
第五條 用枚舉表示狀態(tài)、選項(xiàng)、狀態(tài)嗎
要點(diǎn)
如果把傳遞給某個(gè)方法的選項(xiàng)表示為枚舉類(lèi)型,而多個(gè)選項(xiàng)又可同時(shí)使用,那么就將各選項(xiàng)值定義為2的冪,以便通過(guò)按位或操作將其組合起來(lái)。
用NS_ENUM與NS_OPTIONS宏定義枚舉類(lèi)型,并指明底層數(shù)據(jù)類(lèi)型。這樣可以確保枚舉是用開(kāi)發(fā)者所選的底層數(shù)據(jù)類(lèi)型實(shí)現(xiàn)出來(lái),而不會(huì)采用編譯器所選的類(lèi)型。
在處理枚舉類(lèi)型的switch語(yǔ)句中不要實(shí)現(xiàn)default分支。這樣的話(huà),加入新枚舉之后,編譯器就會(huì)提示開(kāi)發(fā)者:switch語(yǔ)句并未處理所有枚舉。
第六條 理解 “屬性” 這一概念
編譯器會(huì)把“點(diǎn)語(yǔ)法”轉(zhuǎn)換為對(duì)存取方法的調(diào)用,使用“點(diǎn)語(yǔ)法”的效果與直接調(diào)用存取方法相同。
如果使用了屬性,編譯器會(huì)自動(dòng)編寫(xiě)訪(fǎng)問(wèn)這些屬性所需的方法,此過(guò)程叫“自動(dòng)合成(autosynthesis)”。這個(gè)過(guò)程由編譯器在編譯期執(zhí)行,所以編輯器里看不到“合成方法(synthesied method)”的源代碼。除了生成方法代碼,編譯器還要自動(dòng)向類(lèi)中添加適當(dāng)類(lèi)型的實(shí)例變量,并且在屬性名前面加下劃線(xiàn),以此作為實(shí)例變量的名字。也可以在類(lèi)的實(shí)現(xiàn)代碼里通過(guò)@synthesize語(yǔ)法來(lái)指定實(shí)例變量的名字
@implementation EOCPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
有一種辦法能阻止編譯器自動(dòng)合成存取方法,就是使用@dynamic關(guān)鍵字,它會(huì)告訴編譯器:不要自動(dòng)創(chuàng)建實(shí)現(xiàn)屬性所用的實(shí)例變量,也不要為其創(chuàng)建存取方法。而且,在編譯訪(fǎng)問(wèn)屬性的代碼時(shí),即使編譯器發(fā)現(xiàn)沒(méi)有定義存取方法,也不會(huì)報(bào)錯(cuò),它相信這些方法能在運(yùn)行期找到。
@interface EOCPerson : NSManagedObject
@property NSString *firstName;
@property NSString *lastName;
@end
@implementation EOCPerson
@dynamic firstName, lastName;
@end
屬性的特質(zhì):原子性,讀/寫(xiě)權(quán)限,內(nèi)存管理語(yǔ)義,方法名
要點(diǎn)
用@property語(yǔ)法來(lái)定義對(duì)象中所封裝的數(shù)據(jù)
通過(guò)“特質(zhì)”來(lái)指定存儲(chǔ)數(shù)據(jù)所需的正確語(yǔ)義
在設(shè)置屬性所對(duì)應(yīng)的實(shí)例變量時(shí),一定要遵從該屬性所聲明的語(yǔ)義
開(kāi)發(fā)iOS程序時(shí)應(yīng)該使用nonatomic屬性,因?yàn)閍tomic屬性會(huì)嚴(yán)重影響性能
第七條 在對(duì)象內(nèi)部盡量直接訪(fǎng)問(wèn)實(shí)例變量
直接訪(fǎng)問(wèn)實(shí)例變量,不會(huì)觸發(fā) kvo通知。
在寫(xiě)入實(shí)例變量時(shí),通過(guò)設(shè)置方法來(lái)做,在讀取實(shí)例變量時(shí),則直接訪(fǎng)問(wèn)。
在初始化方法中設(shè)置屬性值應(yīng)該直接訪(fǎng)問(wèn)實(shí)例變量,因?yàn)樽宇?lèi)可能會(huì)覆寫(xiě)設(shè)置方法
delloc中直接通過(guò)
惰性化初始技術(shù)需要采用存取方法。
第八條 理解“對(duì)象等同性” 這一概念
使用NSObject協(xié)議中聲明的 isEqual 方法來(lái)判斷兩個(gè)對(duì)象的等同性
NSObject協(xié)議中兩個(gè)用于判斷等同性的關(guān)鍵方法:
- (BOOL)isEqual:(id)object;
- (NSUInterger)hash;
等同性約定
如果 isEqual: 判定兩個(gè)對(duì)象相等,那么hash方法也必須返回同一個(gè)值.但是,如果兩個(gè)對(duì)象的hash方法返回同一個(gè)值, isEqual: 未必會(huì)認(rèn)為兩者相等
有一種情況要注意,在容器中放入可變類(lèi)對(duì)象時(shí),把某個(gè)對(duì)象放入collection之后,就不應(yīng)再改變其哈希碼了。
要點(diǎn):
若想檢測(cè)對(duì)象的等同性,請(qǐng)?zhí)峁癷sEquel:”與hash方法
相同的對(duì)象必須具有相同的哈希碼,但是兩個(gè)哈希碼相同的對(duì)象卻未必相同
第九條 以“類(lèi)族模式”隱藏實(shí)現(xiàn)細(xì)節(jié)
類(lèi)族可以隱藏“抽象基類(lèi)”背后的實(shí)現(xiàn)細(xì)節(jié)。
系統(tǒng)框架有很多類(lèi)族。大部分collection類(lèi)都是類(lèi)族。
在傳統(tǒng)的類(lèi)族模式中,通常只有一個(gè)類(lèi)具備“公共借口”,這個(gè)類(lèi)就是類(lèi)族中的抽象基類(lèi)
Cocoa中NSArray這樣的類(lèi)族來(lái)說(shuō),新增子類(lèi)需遵守幾條規(guī)則:
自類(lèi)應(yīng)該繼承自類(lèi)族中的抽象基類(lèi)
子類(lèi)應(yīng)該定義自己的數(shù)據(jù)存儲(chǔ)方式
子類(lèi)應(yīng)當(dāng)覆寫(xiě)超類(lèi)文檔中指明需要覆寫(xiě)的方法
第十條 在既有類(lèi)中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)

設(shè)置關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
取關(guān)聯(lián)對(duì)象值
id objc_getAssociatedObject(id object, void *key)
移除指定對(duì)象的全部關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(id object)
要點(diǎn)
定義關(guān)聯(lián)對(duì)象可指定內(nèi)存管理語(yǔ)義,用以模仿定義屬性時(shí)所采用的“擁有關(guān)系”與“非擁有關(guān)系”
只有在其他做法不可行時(shí)才應(yīng)選用關(guān)聯(lián)對(duì)象,因?yàn)檫@種做法通常會(huì)引入難于查找的bug
第十一條 理解objc_msgSend的作用
C語(yǔ)言使用“靜態(tài)綁定”,在編譯期就能決定運(yùn)行時(shí)調(diào)用的函數(shù)
在Objective-C中,如果向某個(gè)對(duì)象傳遞消息,那就會(huì)使用動(dòng)態(tài)綁定機(jī)制來(lái)決定需要調(diào)用的方法。在底層,所有方法都是普通的C語(yǔ)言函數(shù),然而對(duì)象收到消息后,究竟該調(diào)用哪個(gè)方法則完全于運(yùn)行期決定,甚至可以在程序運(yùn)行時(shí)改變,這些特性使得Objective-C成為一門(mén)真正的動(dòng)態(tài)語(yǔ)言。
id returnValue = [someObject messageName:parameter];
someObject: 接受者(receiver)
messageName: : 選擇子(selector)
選擇子與參數(shù) 合起來(lái)稱(chēng)為“消息(message)”
編譯器看到此消息后,將其轉(zhuǎn)換為一條標(biāo)準(zhǔn)的C語(yǔ)言函數(shù)調(diào)用,其原型
void objc_msgSend(id self, SEL cmd, ...)
轉(zhuǎn)換后:
id returnValue = objc_msgSend(someObject, @selector(messageName:), parameter);
該方法需要在接收者所屬的類(lèi)中搜尋其“方法列表”,如果能找到與選擇子名稱(chēng)相符的方法,就跳至其實(shí)現(xiàn)代碼。若找不到,就沿著繼承體系繼續(xù)向上查找,等找到合適的方法之后再跳轉(zhuǎn),如果最終還是找不到相符的方法,就執(zhí)行“消息轉(zhuǎn)發(fā)(message forwarding)”
objc_msgSend會(huì)將匹配結(jié)果緩存在“快速映射表”(fast map)里,每個(gè)類(lèi)都有這樣一個(gè)緩存,稍后還向該類(lèi)發(fā)送與選擇子相同的消息,會(huì)快很多,但還是不如“靜態(tài)綁定的函數(shù)操作”那樣迅速。
消息派發(fā)并非應(yīng)用程序的瓶頸所在。假如是瓶頸,可以只編寫(xiě)純C函數(shù)
objc_msgSend等函數(shù)一旦找到應(yīng)該調(diào)用的方法實(shí)現(xiàn)后,就會(huì)“跳轉(zhuǎn)過(guò)去?!敝阅苓@樣,因?yàn)镺bjective-C對(duì)象的每個(gè)方法都可以視為簡(jiǎn)單的C函數(shù),其原型如下
<return_type> Class_selector(id self, SEL _cmd, ...)
每個(gè)類(lèi)里面都有一張表,其中的指針都指向這種函數(shù),而選擇子的名稱(chēng)是查表時(shí)所用的“鍵”。objc_msgSend正式通過(guò)這張表格來(lái)尋找應(yīng)該執(zhí)行的方法并跳至實(shí)現(xiàn)的。
要點(diǎn)
發(fā)給某對(duì)象的全部消息都要由“動(dòng)態(tài)消息派發(fā)系統(tǒng)”(dynamic message dispatch system)來(lái)處理,該系統(tǒng)會(huì)查出對(duì)應(yīng)的方法,并執(zhí)行其代碼。
第十二條 理解消息轉(zhuǎn)發(fā)機(jī)制
當(dāng)對(duì)象接收到無(wú)法解讀的消息后,就會(huì)啟動(dòng)“消息轉(zhuǎn)發(fā)(message forwarding)”機(jī)制,程序員可經(jīng)由此過(guò)程告訴對(duì)象應(yīng)該如何處理未知消息。
消息轉(zhuǎn)發(fā)分為兩大階段。第一階段先征詢(xún)接收者,所屬的類(lèi),看其是否能動(dòng)態(tài)添加方法,以處理當(dāng)前這個(gè)“未知的選擇子(unknown selector)”,這叫做“動(dòng)態(tài)方法解析”(dynamic method resolution)。
第二階段涉及“完整的消息轉(zhuǎn)發(fā)機(jī)制”(full forwarding mechanism)。如果運(yùn)行期系統(tǒng)已經(jīng)把第一階段執(zhí)行完了,那么接收者自己就無(wú)法再以動(dòng)態(tài)新增方法的手段來(lái)響應(yīng)包含該選擇子的消息了。此時(shí),運(yùn)行期系統(tǒng)會(huì)請(qǐng)求接收者以其他手段來(lái)處理與消息相關(guān)的方法調(diào)用。這又細(xì)分為兩小步
a、首先,接收者看看有沒(méi)有其他對(duì)象能處理這條消息。若有,則運(yùn)行期系統(tǒng)會(huì)把消息轉(zhuǎn)給那個(gè)對(duì)象,消息轉(zhuǎn)發(fā)過(guò)程結(jié)束。
b、若沒(méi)有“備援的接收者”(replacement receiver),則啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制,運(yùn)行期系統(tǒng)會(huì)把與消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation對(duì)象中,再給接收者最后一次機(jī)會(huì),令其設(shè)法解決當(dāng)前還未處理的這條消息。
對(duì)象在收到無(wú)法解讀的消息后,首先將調(diào)用其所屬類(lèi)的下列類(lèi)方法:
+ (BOOL)resolveInstanceMethod:(SEL)selector
+ (BOOL)resolveClassMethod:(SEL)selector
使用這種方法的前提是:相關(guān)方法的實(shí)現(xiàn)代碼已經(jīng)寫(xiě)好,只等著運(yùn)行的時(shí)候動(dòng)態(tài)插在類(lèi)里面就可以了。此方案常用來(lái)實(shí)現(xiàn)@dynamic 屬性。
備援接收者:當(dāng)前接收者還有第二次機(jī)會(huì)能處理未知的選擇子,在這一步中,運(yùn)行期系統(tǒng)會(huì)問(wèn)它:能不能把這條消息轉(zhuǎn)給其他接收者來(lái)處理。該步驟對(duì)應(yīng)的處理方法如下:
- (id)forwardingTargetForSelector:(SEL)selector
完整的消息轉(zhuǎn)發(fā)
- (void)forwardInvocation:(NSInvocation *)invocation
這個(gè)方法可以實(shí)現(xiàn)得很簡(jiǎn)單:只需改變調(diào)用目標(biāo),使消息在新目標(biāo)得以調(diào)用即可。然而這樣實(shí)現(xiàn)出來(lái)的方法與“備援接收者”方案所實(shí)現(xiàn)的方法等效,所以很少有人采用這么簡(jiǎn)單的實(shí)現(xiàn)方式。比較有用的實(shí)現(xiàn)方式為:在出發(fā)消息前先以某種方式改變消息內(nèi)容,比如追加另外一個(gè)參數(shù),或者改變選擇子,等等。

接收者在每一步中均有機(jī)會(huì)處理消息。步驟越往后,處理消息的代價(jià)就越大。
第十三條 用“方法調(diào)配技術(shù)” 調(diào)試 “黑盒方法”
類(lèi)的方法列表會(huì)把選擇子的名稱(chēng)映射到相關(guān)的方法實(shí)現(xiàn)上,使得“消息派發(fā)系統(tǒng)”能夠根據(jù)此找到應(yīng)該調(diào)用的方法。這些方法均以函數(shù)指針的形式來(lái)表示,這種指針叫做IMP,其原型如下:
id (*IMP)(id, SEL, ...)
交換方法實(shí)現(xiàn),可用下列函數(shù):
void method_exchangeImpentations(Method m1, Method m2)
此函數(shù)的兩個(gè)參數(shù)表示待交換的兩個(gè)方法實(shí)現(xiàn),而方法實(shí)現(xiàn)則可通過(guò)下列函數(shù)獲得
Method class_getInstanceMethod(Class aClass, SEL aSelector)
要點(diǎn):
使用另一份實(shí)現(xiàn)來(lái)替換原有的方法實(shí)現(xiàn),這道工序叫做“方法調(diào)配”,開(kāi)發(fā)者常用此技術(shù)向原有實(shí)現(xiàn)中添加新功能。
一般來(lái)說(shuō),只有調(diào)試程序的時(shí)候才需要在運(yùn)行期修改方法實(shí)現(xiàn),這種做法不宜濫用
第十四條 理解“類(lèi)對(duì)象”的用意
描述Objective-C對(duì)象所用的數(shù)據(jù)結(jié)構(gòu)定義在運(yùn)行期程序庫(kù)的頭文件里,id類(lèi)型本身也在定義在這里:
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è)類(lèi)僅有一個(gè)“類(lèi)對(duì)象”,而每個(gè)“類(lèi)對(duì)象”僅有一個(gè)與之相關(guān)的“元類(lèi)”
isMemberOfClass: 判斷對(duì)象是否為某個(gè)特定類(lèi)的實(shí)例
isKindOfClass: 對(duì)象是否為某類(lèi)或其派生類(lèi)的實(shí)例
類(lèi)對(duì)象是“單例”,在應(yīng)用程序范圍內(nèi),每個(gè)類(lèi)的Class僅有一個(gè)實(shí)例。
要點(diǎn)
每個(gè)實(shí)例都有一個(gè)指向Class對(duì)象的指針,用以表明其類(lèi)型,而這些Class對(duì)象則構(gòu)成類(lèi)的集成體系
如果對(duì)象類(lèi)型無(wú)法在編譯器確定,那么就應(yīng)該使用類(lèi)型信息查詢(xún)方法來(lái)探知
盡量使用類(lèi)型信息查詢(xún)方法來(lái)確定對(duì)象類(lèi)型,而不要直接比較類(lèi)對(duì)象,因?yàn)槟承?duì)象可能實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)功能
第十五條 用前綴避免命名空間沖突
Objective-C沒(méi)有其他語(yǔ)言語(yǔ)言那種內(nèi)置的命名空間機(jī)制
使用Cocoa創(chuàng)建應(yīng)用程序時(shí)一定要注意,Apple宣稱(chēng)其保留使用所有“兩字母前綴”的權(quán)利,所以你自己選用的前綴應(yīng)該是三字母的。
要點(diǎn)
選擇與你的公司,應(yīng)用程序或二者皆有關(guān)聯(lián)之名稱(chēng)作為類(lèi)名的前綴,并在所有代碼中均使用這一前綴,并在所有代碼中均使用這一前綴
若自己所開(kāi)發(fā)的程序庫(kù)中用到了第三房庫(kù),則應(yīng)為其中的名稱(chēng)加上前綴
第十六條 提供“全能初始化方法”
如果創(chuàng)建類(lèi)實(shí)例的方法不止一種,那么這個(gè)類(lèi)就會(huì)有多個(gè)初始化方法。要再其中選定一個(gè)作為全能初始化方法。
要點(diǎn)
在類(lèi)中提供一個(gè)全能初始化方法,并于文檔里指明。其他初始化方法均應(yīng)調(diào)用此方法
若全能初始化方法與超類(lèi)不同,則需覆寫(xiě)超類(lèi)中的對(duì)應(yīng)方法
如果超類(lèi)的初始化方法不適用于子類(lèi),那么應(yīng)該覆寫(xiě)這個(gè)超類(lèi)方法,并在其中拋出異常
第十七條 實(shí)現(xiàn)description方法
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p, \"%@ %@\">", [self class], self, _firstName, _lastName];
}
//output:
// person = <EOCPerson: 0x7fb249c030f0, "Bob Smith">
要點(diǎn)
實(shí)現(xiàn)description方法返回一個(gè)有意義的字符串,用以描述該實(shí)例
若想在調(diào)試時(shí)打印出更詳細(xì)的對(duì)象描述信息,則應(yīng)實(shí)現(xiàn)debugDescription方法
第十八條 盡量使用不可變對(duì)象
應(yīng)該盡量把對(duì)外公布出來(lái)的屬性設(shè)為只讀,而且只在確有必要時(shí)才將屬性對(duì)外公布。
有時(shí)可能想修改封裝在對(duì)象內(nèi)部的數(shù)據(jù),但是卻不想令這些數(shù)據(jù)為外人所改動(dòng)。這種情況下,通常做法是在對(duì)象內(nèi)部將readonly屬性重新聲明為readwrite
在定義類(lèi)的公共API時(shí),還要注意一件事情:對(duì)象里表示各種collection的那些屬性究竟應(yīng)該設(shè)成可變的還是不可變的。例如,用某個(gè)類(lèi)表示個(gè)人信息,該類(lèi)里還存放了一些引用,指向此人的諸位朋友。把這個(gè)人的全部朋友放在一個(gè)列表里,并將其做成屬性,可以添加或刪除此人的朋友,這個(gè)屬性需要用可變的set來(lái)實(shí)現(xiàn)。在這種情況下,通常應(yīng)該提供一個(gè)readonly屬性供外界使用,該屬性返回不可變的set,而此set是內(nèi)部不可變set的一份拷貝
要點(diǎn)
盡量創(chuàng)建不可變的對(duì)象
若某屬性?xún)H可于對(duì)象內(nèi)部修改,則在“class-continuation分類(lèi)”中將其由readonly屬性擴(kuò)展為readwrite
不要把可變的collection作為屬性公開(kāi),而應(yīng)提供相關(guān)方法,以此修改對(duì)象中的可變collection
第十九條 使用清晰而協(xié)調(diào)的命名方式
不要吝于使用長(zhǎng)方法名。把方法名起得稍微長(zhǎng)一點(diǎn),可以保證其能準(zhǔn)確傳達(dá)出方法所執(zhí)行的任務(wù)
給方法命名時(shí)的注意事項(xiàng):
1、如果方法的返回值是新創(chuàng)建的,那么方法名的首個(gè)詞應(yīng)是返回值的類(lèi)型,除非前面還有修飾語(yǔ),例如localizedString。屬性的存取方法不遵循這種命名方式,因?yàn)橐话阏J(rèn)為這些方法不會(huì)創(chuàng)建新對(duì)象,即便有時(shí)返回內(nèi)部對(duì)象的一份拷貝,我們也認(rèn)為那相當(dāng)于原有的對(duì)象。這些存取方法應(yīng)該按照其所對(duì)應(yīng)的屬性來(lái)命名
2、應(yīng)該把表示參數(shù)類(lèi)型的名詞放在參數(shù)前面。
3、如果方法要在當(dāng)前對(duì)象上執(zhí)行操作,那么就應(yīng)該包含動(dòng)詞,若執(zhí)行操作時(shí)還需要參數(shù),則應(yīng)該在動(dòng)詞后面加上一個(gè)或多個(gè)名詞。
4、不要使用str這種簡(jiǎn)稱(chēng),應(yīng)該用string這樣的全稱(chēng)
5、Boolean屬性應(yīng)加is前綴。如果某方法返回屬性的Boolean值,那么應(yīng)該根據(jù)其功能,選用has或is當(dāng)前綴。
6、將get這個(gè)前綴留給那些借由“輸出參數(shù)”來(lái)保存返回值的方法。
第二十條 為私有方法名加前綴
與公有方法不同,私有方法不出現(xiàn)在接口定義中。有時(shí)可能要在“class-continuation分類(lèi)”里聲明私有方法,然而最近修訂的編譯器已經(jīng)不需要在使用方法前必須先聲明了。所以說(shuō),私有方法一般只在實(shí)現(xiàn)的時(shí)候聲明。
要點(diǎn)
給私有方法的名稱(chēng)加上前綴,這樣可以很容易地將其同公共方法區(qū)分開(kāi)
不要單用一個(gè)下劃線(xiàn)做私有方法前綴,因?yàn)檫@種做法是預(yù)留給蘋(píng)果公司用的
第二十一條 理解Objective-C錯(cuò)誤模型
ARC在默認(rèn)情況下不是異常安全的
Objective-C語(yǔ)言現(xiàn)在所采用的辦法是:只在極其罕見(jiàn)的情況下拋出異常,異常拋出之后,無(wú)須考慮恢復(fù)問(wèn)題,而且應(yīng)用程序此時(shí)也應(yīng)該退出。這就是說(shuō)不用編寫(xiě)復(fù)雜的異常安全代碼。
在出現(xiàn)不嚴(yán)重的錯(cuò)誤時(shí),Objective-C語(yǔ)言所用的編程范式為:令方法返回nil/0,或是使用NSError
要點(diǎn)
只有發(fā)生了可使整個(gè)應(yīng)用程序崩潰的嚴(yán)重錯(cuò)誤時(shí),才應(yīng)使用異常
在錯(cuò)誤不嚴(yán)重情況下,可以指派“委托方法(delegate method)”來(lái)處理錯(cuò)誤,也可以把錯(cuò)誤信息放在NSError對(duì)象里,經(jīng)由“輸出參數(shù)”返回給調(diào)用者
第二十二條 理解NSCopying協(xié)議
如果想令自己的類(lèi)支持copy操作,就要實(shí)現(xiàn)NSCopying協(xié)議,該協(xié)議只有一個(gè)方法
- (id)copyWithZone:(NSZone *)zone
以前開(kāi)發(fā)程序時(shí),會(huì)據(jù)NSZone把內(nèi)存分成不同的區(qū)(zone),而對(duì)象會(huì)創(chuàng)建在某個(gè)區(qū)里面?,F(xiàn)在不用了,每個(gè)程序只有一個(gè)區(qū):默認(rèn)區(qū)(default zone)。所以說(shuō),盡管必須實(shí)現(xiàn)這個(gè)方法,但是不必?fù)?dān)心其中的zone參數(shù)。
copy方法由NSObject實(shí)現(xiàn),該方法只是以默認(rèn)區(qū)為參數(shù)調(diào)用copyWithZone:。我們總是想覆寫(xiě)copy方法,其實(shí)真正需要實(shí)現(xiàn)的卻是copyWithZone:方法。
要點(diǎn)
如果自定義的對(duì)象分為可變版本與不可變版本,那么就要同時(shí)實(shí)現(xiàn)NSCopying與NSMutableCopying協(xié)議
復(fù)制對(duì)象時(shí)需決定采用淺拷貝還是深拷貝,一般情況下應(yīng)該盡量執(zhí)行淺拷貝
如果你所寫(xiě)的對(duì)象需要深拷貝,那么可考慮新增一個(gè)專(zhuān)門(mén)執(zhí)行深拷貝的方法
第二十三條 通過(guò)委托與數(shù)據(jù)源協(xié)議進(jìn)行對(duì)象間通信
Objective-C開(kāi)發(fā)者廣泛使用一種名叫“委托模式(Delegate pattern)”的變成設(shè)計(jì)模式來(lái)實(shí)現(xiàn)對(duì)象間通信的通信,該模式的主旨時(shí):定義一套接口,某對(duì)象若想接受另一個(gè)對(duì)象的委托,則需遵從此接口,以便成為其“委托對(duì)象(delegate)”。而這“另一個(gè)對(duì)象”則可以給委托對(duì)象回傳一些信息,也可以在發(fā)生相關(guān)事件時(shí)通知委托對(duì)象

@protocol EOCNetworkFetcherDelegate
- (void)networkFetcher:(EOCNetworkFetcher *)fetcher didReceiveData:(NSData *)data;
- (void)networkFetcher:(EOCNetworkFetcher *)fetcher didFailWithError:(NSError *)error;
@end
有了協(xié)議后,類(lèi)就可以用一個(gè)屬性來(lái)存放其委托對(duì)象了
@interface EOCNetworkFetcher : NSObject
@property (nonatomic, weak) id <EOCNetworkFetcherDelegate> delegate;
@end
實(shí)現(xiàn)委托對(duì)象的的辦法:聲明某個(gè)類(lèi)遵從委托協(xié)議,然后把協(xié)議中想實(shí)現(xiàn)的那些方法在類(lèi)里實(shí)現(xiàn)??梢栽诮涌谥新暶鳎部梢栽凇癱lass-continuation分類(lèi)”中聲明。
@interface EOCDataModel () <EOCNetworkFetcherDelegate>
@end
@implementation EOCDataModel
- (void)networkFetcher:(EOCNetworkFetcher *)fetcher didReceiveData:(NSData *)data {
}
- (void)networkFetcher:(EOCNetworkFetcher *)fetcher didFailWithError:(NSError *)error {
}
@end
委托對(duì)象上調(diào)用可選方法
NSData *data = xxx;
if ([_delegate respondsToSelector:@selector(networkFetcher:didReceiveData:)) {
[_delegate networkFetcher:self didReceiveData:data];
}

通常把委托對(duì)象能否響應(yīng)某個(gè)協(xié)議方法這一信息緩存起來(lái),以?xún)?yōu)化程序效率
第二十四條 將類(lèi)的實(shí)現(xiàn)代碼分散到便于管理的數(shù)個(gè)分類(lèi)之中
要點(diǎn)
使用分類(lèi)機(jī)制把類(lèi)的實(shí)現(xiàn)代碼劃分成易于管理的小塊
將應(yīng)該視為“私有”的方法歸入名為Private的分類(lèi)中,以隱藏實(shí)現(xiàn)細(xì)節(jié)
第二十五條 總是為第三方類(lèi)的分類(lèi)名稱(chēng)加前綴
分類(lèi)中的方法是直接添加在類(lèi)里面,它們就好比這個(gè)類(lèi)中的固有方法。將分類(lèi)方法加入類(lèi)中這一操作是在運(yùn)行期系統(tǒng)加載分類(lèi)時(shí)完成的。運(yùn)行期系統(tǒng)會(huì)把分類(lèi)中所實(shí)現(xiàn)的每個(gè)方法都加入類(lèi)的方法列表中。如果類(lèi)中本來(lái)就有此方法,而分類(lèi)又實(shí)現(xiàn)了一次,那么分類(lèi)中的方法會(huì)覆蓋原來(lái)那一份實(shí)現(xiàn)代碼。實(shí)際上可能會(huì)發(fā)生很多次覆蓋,比如某個(gè)分類(lèi)中的方法覆蓋了“主實(shí)現(xiàn)”中的相關(guān)方法,而另一個(gè)分類(lèi)中的方法又覆蓋了這個(gè)分類(lèi)中的方法。多次覆蓋的結(jié)果以最后一個(gè)分類(lèi)為準(zhǔn)。
要點(diǎn):
向第三方類(lèi)中添加分類(lèi)時(shí),總應(yīng)該給其名稱(chēng)加上你專(zhuān)有的前綴
向第三方類(lèi)中添加分類(lèi)時(shí),總應(yīng)該給其中的方法名加上你專(zhuān)有的前綴
第二十六條 勿在分類(lèi)中聲明屬性
盡管從技術(shù)上說(shuō),分類(lèi)里也可以聲明屬性,但這種做法還是要盡量避免。原因在于,除了“class-continuation分類(lèi)”之外,其他分類(lèi)都無(wú)法向類(lèi)中新增實(shí)例變量,因此,它們無(wú)法把實(shí)現(xiàn)屬性所需的實(shí)例變量合成出來(lái)。
開(kāi)發(fā)者需要在分類(lèi)中為屬性實(shí)現(xiàn)存取方法。此時(shí)可以把存取方法聲明為@dynamic,也就是說(shuō),這些方法等到運(yùn)行期再提供,編譯器目前是看不見(jiàn)的。如果決定使用消息轉(zhuǎn)發(fā)機(jī)制在運(yùn)行期攔截方法調(diào)用,并提供實(shí)現(xiàn),那么或許可以采用這種方法。
關(guān)聯(lián)對(duì)象能夠解決在分類(lèi)中不能合成實(shí)例變量的問(wèn)題。
#import <objc/runtime.h>
static const char *kFriendsPropertyKey = "kFriendsPropertyKey";
@implementation EOCPerson (Friendship)
- (NSArray *)friends {
return objc_getAssociatedObject(self, kFriendsPropertyKey);
}
- (void)setFriends:(NSArray *)frends {
objc_setAssociatedOjbect(self, kFriendsPropertyKey, friends, OBJC_ASSOCIATION__RETAIN_NONATOMIC);
}
本例中,正確做法是把所有屬性都定義在主接口里。類(lèi)所封裝的全部數(shù)據(jù)都應(yīng)該定義在主接口中,這里是唯一能夠定義實(shí)例變量(也就是數(shù)據(jù))的地方。而屬性知識(shí)定義實(shí)例變量及相關(guān)存取方法的語(yǔ)法糖,所以也應(yīng)遵循同實(shí)例變量一樣的規(guī)則。至于分類(lèi)機(jī)制,則應(yīng)將其理解為一種手段,目標(biāo)在于擴(kuò)展類(lèi)的功能,而非封裝數(shù)據(jù)。
要點(diǎn)
把封裝數(shù)據(jù)所用的全部屬性都定義在主接口里
在“class-continuation分類(lèi)”之外的其他分類(lèi)中,可以定義存取方法,但盡量不要定義屬性
第二十七條 使用“class-continuation分類(lèi)”隱藏實(shí)現(xiàn)細(xì)節(jié)
Objective-C動(dòng)態(tài)消息系統(tǒng)的工作方式?jīng)Q定了其不可能實(shí)現(xiàn)真正的私有方法或私有變量
“class-continuation分類(lèi)”和普通分類(lèi)不同,它必須定義在其所連續(xù)的那個(gè)類(lèi)的實(shí)現(xiàn)文件里。其重要之處在于,這是唯一能聲明實(shí)例變量的分類(lèi),而且此分類(lèi)沒(méi)有特定的實(shí)現(xiàn)文件,其中的方法都應(yīng)該定義在類(lèi)的主實(shí)現(xiàn)文件里。與其他分類(lèi)不同,“class-continuation分類(lèi)”沒(méi)有名字
@interface EOCPerson () {
NSString *_anInstanceVariable;
}
//Method declarations here
@end
@implementation EOCPerson {
int _anotherInstanceVariable;
}
//Method implementations here
@end
"class-continuation分類(lèi)"還有一種合理用法,就是將public接口中聲明為“只讀”的屬性擴(kuò)展為“可讀寫(xiě)”
只會(huì)在類(lèi)的實(shí)現(xiàn)中用到的私有方法也可以聲明在“class-continuation分類(lèi)”中。
新版編譯器不強(qiáng)制要求開(kāi)發(fā)者在使用方法前必須先聲明。
若對(duì)象所遵從的協(xié)議只應(yīng)視為私有,則可在“class-continuation分類(lèi)”中聲明
第二十八條 通過(guò)協(xié)議提供匿名對(duì)象
要點(diǎn)
協(xié)議可在某種程度上提供匿名類(lèi)型。具體的對(duì)象類(lèi)型可以淡化成遵從某協(xié)議的id類(lèi)型,協(xié)議里規(guī)定了對(duì)象所應(yīng)實(shí)現(xiàn)的方法
使用匿名對(duì)象來(lái)隱藏類(lèi)型名稱(chēng)
如果具體類(lèi)型不重要,重要的是對(duì)象能夠響應(yīng)(定義在協(xié)議里的)特定方法,那么可使用匿名對(duì)象來(lái)表示
第二十九條 理解引用計(jì)數(shù)
retain 遞增保留計(jì)數(shù)
release 遞減保留計(jì)數(shù)
autorelease 待稍后清理“自動(dòng)釋放池”(autorelease pool)時(shí),再遞減保留計(jì)數(shù)
查看保留計(jì)數(shù)的方法叫retainCount,此方法不太有用,不推薦使用這個(gè)方法

按“引用樹(shù)”回溯,那么最終會(huì)發(fā)現(xiàn)一個(gè)“根對(duì)象”(root object)。在Mac OS X應(yīng)用程序中,此對(duì)象就是NSApplication對(duì)象;而在iOS應(yīng)用程序中,則是 UIApplication對(duì)象。兩者都是應(yīng)用程序啟動(dòng)時(shí)創(chuàng)建的單例
調(diào)用autorelease,此方法會(huì)在稍后遞減計(jì)數(shù),通常是在下一次“事件循環(huán)”(event loop)時(shí)遞減,不過(guò)也可能執(zhí)行得更早些。
此方法可以保證對(duì)象在跨越“方法調(diào)用邊界”(method call boundary)后一定存活。實(shí)際上,釋放操作會(huì)在清空最外層的自動(dòng)釋放池時(shí)執(zhí)行,除非你有自己的自動(dòng)釋放池,否則這個(gè)時(shí)機(jī)指的就是當(dāng)前線(xiàn)程的下一次事件循環(huán)。
要點(diǎn)
引用計(jì)數(shù)機(jī)制通過(guò)可以遞增遞減的計(jì)數(shù)器來(lái)管理內(nèi)存。對(duì)象創(chuàng)建好之后,其保留計(jì)數(shù)至少為1。若保留計(jì)數(shù)為正,則對(duì)象繼續(xù)存活。當(dāng)保留計(jì)數(shù)降為0時(shí),對(duì)象就被銷(xiāo)毀了。
在對(duì)象生命期中,其余對(duì)象通過(guò)引用來(lái)保留或釋放此對(duì)象。保留與釋放操作分別會(huì)遞增及遞減保留計(jì)數(shù)
第三十條 以ARC簡(jiǎn)化引用計(jì)數(shù)
使用ARC時(shí)一定要記住,引用計(jì)數(shù)實(shí)際上還是要執(zhí)行的,只不過(guò)保留與釋放操作現(xiàn)在是由ARC自動(dòng)為你添加。
由于ARC會(huì)自動(dòng)執(zhí)行retain,release,autorelease等操作,所以直接在ARC下調(diào)用這些內(nèi)存管理方法是非法的。具體來(lái)說(shuō),不能調(diào)用 retain,release,autorelease,dealloc
將內(nèi)存管理語(yǔ)義在方法名中表示出來(lái)早已成為Objective-C的慣例,而ARC則將之確立為硬性規(guī)定。這些規(guī)則簡(jiǎn)單地體現(xiàn)在方法名上。若方法名以下列詞語(yǔ)開(kāi)頭,則其返回的對(duì)象歸調(diào)用者所有:alloc,new,copy,mutableCopy
若方法名不以上述四個(gè)詞語(yǔ)開(kāi)頭,則表示其返回的對(duì)象并不歸調(diào)用者所有。這種情況下,返回的對(duì)象會(huì)自動(dòng)釋放,所以其值在跨越方法調(diào)用邊界后依然有效。
使用ARC還有其他好處,它可以執(zhí)行一些手工操作很難甚至無(wú)法完成的優(yōu)化。例如,編譯期,ARC會(huì)把能夠互相抵消的retain、release、autorelease操作簡(jiǎn)約。如果發(fā)現(xiàn)在同一個(gè)對(duì)象上執(zhí)行了多次“保留”與“釋放”操作,那么ARC有時(shí)可以成對(duì)地移除這兩個(gè)操作。
ARC也會(huì)處理局部變量與實(shí)例變量的內(nèi)存管理。默認(rèn)情況下,每個(gè)變量都是指向?qū)ο蟮膹?qiáng)引用。
@interface EOCClass : NSObject {
id __weak _weakObject;
id __unsafe_unretained _unsafeUnretainedObject;
}
不論采用上面哪種寫(xiě)法,在設(shè)置實(shí)例變量時(shí)都不會(huì)保留其值。只有使用新版(Mac OS X 10.7,iOS 5.0及其后續(xù)版本)運(yùn)行期程序庫(kù)時(shí),加了__weak修飾符的weak引用才會(huì)自動(dòng)清空。
塊會(huì)自動(dòng)保留其所捕獲的全部對(duì)象,而如果這其中有某個(gè)對(duì)象又保留了塊本身,那么就可能導(dǎo)致“保留環(huán)”。可以用__weak局部變量來(lái)打破這種“保留環(huán)”
用了ARC后,就不需要再編寫(xiě)這種dealloc方法了,不過(guò)如果有非Objective-C的對(duì)象,比如CoreFoundation中的對(duì)象或是由malloc()分配在堆中的內(nèi)存,那么仍然需要清理。
- (void)dealloc {
CFRelease(_coreFoundationObject);
free(_heapAllocateMemoryBlob);
}
要點(diǎn)
ARC只負(fù)責(zé)管理Objective-C對(duì)象的內(nèi)存。尤其要注意:CoreFoundation對(duì)象不歸ARC管理,開(kāi)發(fā)者必須適時(shí)調(diào)用CFRetain/CFRelease。
第三十一條:在dealloc方法中只釋放引用并解除監(jiān)聽(tīng)
在dealloc方法中,通常還要做一件事,那就是把原來(lái)配置過(guò)的觀測(cè)行為(observation behavior)都清理掉。如果用NSNotificationCenter給此對(duì)象訂閱過(guò)某種通知,那么一般應(yīng)該在這里注銷(xiāo),這樣的話(huà),通知系統(tǒng)就不再把通知發(fā)給回收后的對(duì)象了,若是還向其發(fā)送通知,則必然會(huì)令應(yīng)用程序崩潰
- (void)dealloc {
CFRelease(coreFoundationObject);
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
在dealloc里也不要調(diào)用屬性的存取方法,因?yàn)橛腥丝赡軙?huì)覆寫(xiě)這些方法,并于其中做一些無(wú)法在回首階段安全執(zhí)行的操作。此外,屬性可能正處于“鍵值觀測(cè)”(Key-Value Observation, KVO)機(jī)制的監(jiān)控之下,該屬性的觀察者(observer)可能會(huì)在屬性值改變時(shí)保留或使用這個(gè)即將回收的對(duì)象。這種做法會(huì)令運(yùn)行期系統(tǒng)的狀態(tài)完全失調(diào),從而導(dǎo)致一些莫名其妙的錯(cuò)誤。
要點(diǎn)
執(zhí)行異步任務(wù)的方法不應(yīng)該在dealloc里調(diào)用;只能在正常狀態(tài)下執(zhí)行的那些方法也不應(yīng)在dealloc里調(diào)用,因?yàn)榇藭r(shí)對(duì)象已經(jīng)處在回收的狀態(tài)了。
第三十二條 編寫(xiě)“異常安全代碼”時(shí)留意內(nèi)存管理問(wèn)題
異常處理例程將銷(xiāo)毀對(duì)象,然而在手動(dòng)管理引用技術(shù)時(shí),銷(xiāo)毀工作有些麻煩
EOCSomeClass *object;
@try {
object =[ [EOCSomeClass alloc] init];
[object doSomethingThatMayThrow];
}
@catch (...) {
NSLog(@"Whoops, there was an error. Oh well...");
}
@finally {
[object release];
}
在ARC下,問(wèn)題更嚴(yán)重
@try {
EOCSomeClass *object =[ [EOCSomeClass alloc] init];
[object doSomethingThatMayThrow];
}
@catch (...) {
NSLog(@"Whoops, there was an error. Oh well...");
}
你可能會(huì)認(rèn)為這種情況ARC自然會(huì)處理的。但實(shí)際上ARC不會(huì)自動(dòng)處理。
-fobjc-arc-exceptions:開(kāi)啟異常捕捉,默認(rèn)不開(kāi)啟
不開(kāi)啟的原因:在Objective-C代碼中,只有當(dāng)應(yīng)用程序必須因異常狀況而終止時(shí)才應(yīng)拋出異常。因此,如果應(yīng)用程序即將終止,那么是否還會(huì)發(fā)生內(nèi)存泄漏就無(wú)關(guān)緊要了。在應(yīng)用程序必須立即終止的情況下,還去添加安全處理異常所用的附加代碼是沒(méi)有意義的。
有種情況編譯器會(huì)自動(dòng)把-fobjc-arc-exceptions標(biāo)志打開(kāi),就是處于Objective-C++模式時(shí)
要點(diǎn)
捕獲異常時(shí),一定要注意將try塊內(nèi)所創(chuàng)立的對(duì)象清理干凈。
在默認(rèn)情況下,ARC不生成安全處理異常所需的清理代碼。開(kāi)啟編譯器標(biāo)志后,可生成這種代碼,不過(guò)會(huì)導(dǎo)致應(yīng)用程序變大,而且會(huì)降低運(yùn)行效率
第三十三條 以弱引用避免保留環(huán)
用unsafe_unretained修飾的屬性特質(zhì),其語(yǔ)義同assign特質(zhì)等價(jià)。然而,assign通常只用于“整體類(lèi)型”(int、float、結(jié)構(gòu)體等),unsafe_unretained則多用于對(duì)象類(lèi)型。這個(gè)詞本身就表明其所修飾的屬性可能無(wú)法安全使用。
weak引用可以自動(dòng)清空,也可以不自動(dòng)清空。自動(dòng)清空(autonilling)是隨著ARC而引入的新特性,由運(yùn)行期系統(tǒng)來(lái)實(shí)現(xiàn)。在具備自動(dòng)清空功能的弱引用上,可以隨意讀取其數(shù)據(jù),因?yàn)檫@種引用不會(huì)指向已經(jīng)回收過(guò)的對(duì)象。
第三十四條 以“自動(dòng)釋放池塊”降低內(nèi)存峰值
@autoreleasepool {
}
int main(int argc, char *argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, @"EOCAppDelegate");
}
}
要點(diǎn)
自動(dòng)釋放池排布棧中,對(duì)象收到autorelease消息后,系統(tǒng)將其放入最頂端的池里
合理運(yùn)用自動(dòng)釋放池,可降低應(yīng)用程序的內(nèi)存峰值
@autoreleasepool這種新式寫(xiě)法能夠創(chuàng)建出更為輕便的自動(dòng)釋放池
第三十五條 用“僵尸對(duì)象”調(diào)試內(nèi)存管理問(wèn)題
Cocoa提供了“僵尸對(duì)象”這個(gè)功能。啟用這項(xiàng)調(diào)試功能之后,運(yùn)行期系統(tǒng)會(huì)把所有已經(jīng)回收的實(shí)例轉(zhuǎn)化為特殊的“僵尸對(duì)象”,而不會(huì)真正回收它們。這種對(duì)象所在的核心內(nèi)存無(wú)法重用,因此不可能遭到覆寫(xiě)。僵尸對(duì)象收到消息后,會(huì)拋出異常,其中準(zhǔn)確說(shuō)明了發(fā)送過(guò)來(lái)的消息,并描述了回首之前的那個(gè)對(duì)象。
將NSZombieEnabled環(huán)境變量設(shè)為YES,即可開(kāi)啟此功能。比方說(shuō),在Mac OSX系統(tǒng)中用bash運(yùn)行程序時(shí),可以這么做
export NSZombieEnabled="YES"
./app
要點(diǎn)
系統(tǒng)會(huì)修改對(duì)象的isa指針,令其指向特殊的僵尸類(lèi),從而使該對(duì)象變?yōu)榻┦瑢?duì)象。僵尸類(lèi)能夠響應(yīng)所有的選擇子,響應(yīng)方式為:打印一條包含消息內(nèi)容及其接受著的消息,然后終止應(yīng)用程序。
第三十六條 不要使用retainCount
此方法之所以無(wú)用,其首要原因在于:他所返回的保留計(jì)數(shù)只是某個(gè)給定時(shí)間上的值。該方法并未考慮到系統(tǒng)會(huì)稍后把自動(dòng)釋放池清空,因?yàn)椴粫?huì)將后續(xù)的釋放操作從返回值里減去,這樣的話(huà),此值就未必能真實(shí)反映實(shí)際的保留計(jì)數(shù)了。
retainCount可能永遠(yuǎn)不返回0,因?yàn)橛袝r(shí)系統(tǒng)會(huì)優(yōu)化對(duì)象的釋放行為,在保留技術(shù)還是1的時(shí)候就把它回首了。只有在系統(tǒng)不打算這么優(yōu)化時(shí),計(jì)數(shù)值才會(huì)遞減至0.
要點(diǎn)
引入ARC后,retainCount方法就正式廢止了,在ARC下調(diào)用該方法會(huì)導(dǎo)致編譯器報(bào)錯(cuò)
第三十七條 理解“塊”這一概念
塊用^符號(hào)表示,后面跟著一對(duì)花括號(hào),括號(hào)里面是塊的實(shí)現(xiàn)代碼。
^{
//Block implementation here
}
塊類(lèi)型的語(yǔ)法與函數(shù)指針近似。下面列出的塊,沒(méi)有參數(shù),也不返回值
void (^someBlock)() = ^{
//Block implementation here
};
塊類(lèi)型的語(yǔ)法結(jié)構(gòu)如下:
return_type (^block_name) (parameters)
下面這種寫(xiě)法所定義的塊,返回nil,并且接受兩個(gè)int做參數(shù):
int (^addBlock)(int a, int b) = ^(int a, int b) {
return a + b;
};
int add = addBlock(2, 5);
塊的強(qiáng)大之處是:在聲明它的范圍里,所有變量都可以為其所捕獲。
int additional = 5;
int (^addBlock)(int a, int b) = ^(int a, int b) {
return a + b + additional;
};
int add = addBlock(2, 5);
默認(rèn)情況下,為塊所捕獲的變量,是不可以在塊里修改的。不過(guò),聲明變量的時(shí)候可以加上__block修飾符,就可以在塊內(nèi)修改了。
如果塊捕獲的變量是對(duì)象類(lèi)型,那么就會(huì)自動(dòng)保留它。系統(tǒng)在釋放這個(gè)塊時(shí),也會(huì)將其一并釋放。塊本身也和其他對(duì)象一樣,有引用計(jì)數(shù)。當(dāng)最后一個(gè)指向塊的引用移走后,塊就回收了?;厥諘r(shí)也會(huì)釋放塊所捕獲的變量,以便平衡捕獲時(shí)所執(zhí)行的保留操作。
如果將塊定義在類(lèi)的實(shí)例方法中,除了可以訪(fǎng)問(wèn)類(lèi)的所有實(shí)例變量外,還可以使用self變量。塊總能修改實(shí)例變量,所以在聲明時(shí)無(wú)須加__block。不過(guò),如果通過(guò)讀取或?qū)懭氩僮鞑东@了實(shí)例變量,那么也會(huì)自動(dòng)把self變量一并捕獲,因?yàn)閷?shí)例變量與self所指代的實(shí)例關(guān)聯(lián)在一起的。
定義塊的時(shí)候,其所占的內(nèi)存區(qū)域是分配在棧中的。這就是說(shuō),塊只在定義它的那個(gè)范圍內(nèi)有效。
不安全:
void (^block) ();
if (/*some condition*/) {
block = ^{
NSLog(@"Block A");
}
}
else? {
block = ^{
NSLog(@"Block B");
};
}
block();
為解決此問(wèn)題,可給塊對(duì)象發(fā)送copy消息以拷貝之。
void (^block) ();
if (/*some condition*/) {
block = [^{
NSLog(@"Block A");
} copy];
}
else? {
block =[ ^{
NSLog(@"Block B");
} copy];
}
block();
要點(diǎn)
塊可以分配在?;蚨焉?,也可以是全局的。分配在棧上的塊可拷貝到堆里。
第三十八條 為常用的塊類(lèi)型創(chuàng)建typedef
為隱藏復(fù)雜的塊類(lèi)型,需要用到C語(yǔ)言中名為“類(lèi)型定義”的特性。
typedef int(^EOCSomeBlock)(BOOL flag, int value);
EOCSomeBlock block = ^(BOOL flag, int value){
//Implementation
};
第三十九條 用handler塊降低代碼分散程度
與使用委托模式的代碼相比,用塊寫(xiě)出來(lái)的代碼顯然更為整潔。
建議使用同一個(gè)塊來(lái)處理成功與失敗情況。
要點(diǎn)
在創(chuàng)建對(duì)象時(shí),可以使用內(nèi)聯(lián)的handler塊將相關(guān)業(yè)務(wù)邏輯一并聲明
在有多個(gè)實(shí)例需要監(jiān)控時(shí),如果采用委托模式,那么經(jīng)常需要根據(jù)傳入的對(duì)象來(lái)切換,若改為handler塊來(lái)實(shí)現(xiàn),則可直接將塊與相關(guān)對(duì)象放在一起。
設(shè)計(jì)API時(shí)如果用到了handler塊,那么可以增加一個(gè)參數(shù),使調(diào)用者可通過(guò)此參數(shù)來(lái)決定應(yīng)該把塊安排在哪個(gè)隊(duì)列上執(zhí)行
第四十條 用塊引用其所屬對(duì)象時(shí)不要出現(xiàn)保留環(huán)
要點(diǎn)
如果塊所捕獲的對(duì)象直接或間接地保留了塊本身,那么就得當(dāng)心保留環(huán)問(wèn)題
一定要找個(gè)適當(dāng)?shù)臋C(jī)會(huì)接觸保留環(huán),而不能把責(zé)任推給API的調(diào)用者。
第四十一條 多用派發(fā)隊(duì)列,少用同步鎖
在OC中,如果有多個(gè)線(xiàn)程要執(zhí)行同一份代碼,那么有時(shí)可能會(huì)出問(wèn)題。這種情況下,通常要使用鎖來(lái)實(shí)現(xiàn)某種同步機(jī)制。在GCD出現(xiàn)之前,有兩種辦法,第一種是采用內(nèi)置的“同步塊”(synchroniztion block):
- (void)synchronizedMethod {
@synchronized(self) {
//Safe
}
}
另一個(gè)辦法是直接使用NSLock對(duì)象:
_lock = [[NSLock allock] init];
- (void)synchronizedMethod {
[_lock lock];
//Safe
[_lock unlock];
}
有種簡(jiǎn)單而高效的辦法可以替代同步塊或鎖對(duì)象,那就是使用“串行同步隊(duì)列”,將讀取操作及寫(xiě)入操作都安排在同一個(gè)隊(duì)列里,既可保證數(shù)據(jù)同步。其用法如下:
_syncQueue = dispatch_queue_create("com.effectiveobjectivec.syncQueue", NULL);
-(NSString *)someString {
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString *)someString {
dispatch_sync(_syncQueue, ^{
_someString = someString;
});
}
可以進(jìn)一步優(yōu)化,設(shè)置方法并不一定非得是同步的。設(shè)置實(shí)例變量所用的塊,并不需要向設(shè)置方法返回什么值。也就是說(shuō),設(shè)置方法的代碼可以改成下面這樣:
- (void)setSomeString:(NSString *)someString {
dispatch_async(_syncQueue, ^{
_someString = someString;
});
}
但這么改有個(gè)壞處:如果你測(cè)以下程序性能,那么可能會(huì)發(fā)現(xiàn)這種寫(xiě)法比原來(lái)慢,因?yàn)閳?zhí)行異步派發(fā)時(shí),需要拷貝塊。若拷貝塊所用的時(shí)間明顯超過(guò)執(zhí)行塊所花的時(shí)間,則這種做法將比原來(lái)更慢,若派發(fā)給隊(duì)列的塊要執(zhí)行更繁重的任務(wù),那么可考慮這種備選方案。
多個(gè)獲取方法可以并發(fā)進(jìn)行,而獲取方法和設(shè)置方法之間不能并發(fā)執(zhí)行。這次不用串行隊(duì)列,改用并行隊(duì)列.
在隊(duì)列中,柵欄塊必須單獨(dú)執(zhí)行,不能與其他塊并行。這只對(duì)并發(fā)隊(duì)列有意義,因?yàn)榇嘘?duì)列中的塊總是按照順序逐個(gè)來(lái)執(zhí)行的。
_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-(NSString *)someString {
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString *)someString {
dispatch_barrier_async(_syncQueue, ^{
_someString = someString;
});
}

注意:設(shè)置函數(shù)也可以改為同步的柵欄塊來(lái)實(shí)現(xiàn),那樣做可能更高效,其原因剛才已經(jīng)解釋過(guò)。
第四十二條 多用GCD, 少用performSelector系列方法
下面兩行代碼的執(zhí)行效果相同
[object performSelector:@selector(selectorName)];
[object selectorName];
在ARC下可能內(nèi)存泄漏,因?yàn)榫幾g器并不知道將要調(diào)用的選擇子是什么,因此,也就不了解其方法簽名及返回值,甚至連是否有返回值都不清楚。而且,由于編譯器不知道方法名,所以就沒(méi)辦法運(yùn)行ARC的內(nèi)存管理規(guī)則來(lái)判定返回值是不是應(yīng)該釋放。鑒于此,ARC采用了比較謹(jǐn)慎的做法,就是不添加釋放操作。然而這么做坑能導(dǎo)致內(nèi)存泄漏,因?yàn)榉椒ㄔ诜祷貙?duì)象時(shí)可能已經(jīng)將其保留了。
另一個(gè)原因,返回值只能是void或?qū)ο箢?lèi)型,若返回值的類(lèi)型為C語(yǔ)言結(jié)構(gòu)體,則不可使用performSelector方法。
延后操作有兩種,優(yōu)先考慮第二種
[self performSelector:@selector(doSomething)
? withObject:nil
? ? afertDelay:5.0];
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^(void) {
[self doSomething];
});
要點(diǎn)
performSelecor系列方法所能處理的選擇子太過(guò)局限了,選擇子的返回值類(lèi)型及發(fā)送給方法的參數(shù)個(gè)數(shù)都受到限制
第四十三條 掌握GCD及操作隊(duì)列的使用時(shí)機(jī)
GCD與操作隊(duì)列區(qū)別:首先GCD是純C的API,而操作隊(duì)列則是Objective-C的對(duì)象。
使用NSOperation及NSOperationQueue的好處如下:
取消某個(gè)操作。
指定操作間的依賴(lài)關(guān)系。一個(gè)操作可以依賴(lài)其他多個(gè)操作。開(kāi)發(fā)者能夠指定操作之間的依賴(lài)體系,使特定的操作必須在另外一個(gè)操作順利執(zhí)行完畢后方可執(zhí)行。
通過(guò)鍵值觀測(cè)機(jī)制(KVO)來(lái)監(jiān)聽(tīng),比如可以通過(guò)isCancelled屬性來(lái)判斷任務(wù)是否取消,又比如可以通過(guò)isFinished屬性來(lái)判斷任務(wù)是否已完成。
指定操作的優(yōu)先級(jí)。操作的優(yōu)先級(jí)表示此操作與隊(duì)列中其他操作之間的優(yōu)先關(guān)系。
重用NSOperation對(duì)象。
要點(diǎn)
在解決多線(xiàn)程與任務(wù)管理問(wèn)題時(shí),派發(fā)隊(duì)列并非唯一方案
操作隊(duì)列提供了一套高層的OC API,能實(shí)現(xiàn)純GCD所具備的絕大部分功能,而且還能完成一些更為復(fù)雜的操作,那些操作若改用GCD來(lái)實(shí)現(xiàn),則需另外編寫(xiě)代碼。
第四十四條 通過(guò)Dispatch Group機(jī)制,根據(jù)系統(tǒng)資源狀況來(lái)執(zhí)行任務(wù)
創(chuàng)建dispatch group
dispatch_group_t dispatch_group_create();
任務(wù)編組
方法一
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
方法二
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);
等待dispatch group執(zhí)行完畢:
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
除了可以用上面的函數(shù)外,還可以換個(gè)方法,使用下列函數(shù):
void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
可以向此函數(shù)傳入塊,等dispatch group執(zhí)行完畢之后,塊會(huì)在特定的線(xiàn)程執(zhí)行。假如當(dāng)前線(xiàn)程不應(yīng)阻塞,而開(kāi)發(fā)者又想在那些任務(wù)全部完成時(shí)得到通知,那么此做法就很有必要了
dispatch_queue_t queue = dispatch_get_globla_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t dispatchGroup = dispatch_group_create();
for (id object in collection) {
dispatch_group_async(disaptchGroup, queue, ^{
[object performTask];
});
}
dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
//Continue processing after completing tasks
若當(dāng)前線(xiàn)程不應(yīng)阻塞,則可用notify
dispatch_queue_t notifyQueue = dispatch_get_main_queue();
dispatch_group_notify(dispatchGroup, notifyQueue, ^{
//Continue processing after completing tasks
});
在前面的范例代碼中,我們遍歷某個(gè)collection,并在其每個(gè)元素執(zhí)行任務(wù),而這也可以用另一個(gè)GCD函數(shù)實(shí)現(xiàn)
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void(^block)(size_t));
此函數(shù)會(huì)反復(fù)執(zhí)行一定次數(shù),每次傳給塊的參數(shù)值都會(huì)遞增,從0開(kāi)始,直至iterations - 1
dispatch_queue_t queue = dispatch_queue_create("com.effectiveobjectives.queue", NULL);
dispatch_apply(10, queue, ^(size_t i){
//perform task
});
可以用并發(fā)隊(duì)列
dispatch_apply會(huì)持續(xù)阻塞,直到所有任務(wù)都執(zhí)行完畢為止。由此可見(jiàn),假如把塊派給了當(dāng)前隊(duì)列,就講導(dǎo)致鎖死。
第四十五條 使用dispatch_once來(lái)執(zhí)行只需運(yùn)行一次的線(xiàn)程安全代碼
+sharedInstance {
static EOCClass *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
要點(diǎn)
經(jīng)常要編寫(xiě)“只需執(zhí)行一次的線(xiàn)程安全代碼”。通過(guò)GCD所提供的dispatch_once函數(shù),很容易就能實(shí)現(xiàn)此功能。
標(biāo)記應(yīng)該聲明在static或global作用域中,這樣的話(huà),在把只需執(zhí)行一次的塊傳給dispatch_once函數(shù)時(shí),穿進(jìn)去的標(biāo)記也是相同的。
第四十六條 不要使用dispatch_get_current_queue
該函數(shù)有種典型錯(cuò)誤用法,就是用它檢測(cè)當(dāng)前隊(duì)列是不是某個(gè)特定的隊(duì)列,試圖以此來(lái)避免執(zhí)行同步派發(fā)時(shí)可能遭遇的死鎖問(wèn)題。
dispatch_queue_t queueA = dispatch_queue_create("com.effectiveobjectivec.queueA", NULL);
dispatch_queue_t queueB = dispatch_queue_create("com.effectiveobjectivec.queueB", NULL);
dispatch_sync(queueA, ^{
dispatch_sync(queueB, ^{
dispatch_blcok_t block = ^{ /*...*/};
if (dispatch_get_current_queue() == queueA) {
block();
}
else {
dispatch_sync(queueA, block);
}
});
});
要解決這個(gè)問(wèn)題,最好的辦法就是通過(guò)GCD所提供的功能來(lái)設(shè)定“隊(duì)列特有數(shù)據(jù)”,此功能可以把任意數(shù)據(jù)以鍵值對(duì)的形式關(guān)聯(lián)到隊(duì)列里。最重要之處在于,假如根據(jù)制定的鍵過(guò)去不到關(guān)聯(lián)數(shù)據(jù),大么系統(tǒng)就會(huì)沿著層級(jí)體系向上查找,直至找到數(shù)據(jù)或到達(dá)根隊(duì)列為止。
dispatch_queue_t queueA = dispatch_queue_create("com.effectiveobjectivec.queueA", NULL);
dispatch_queue_t queueB = dispatch_queue_create("com.effectiveobjectivec.queueB", NULL);
dispatch_set_target_queue(queueB, queueA);
static int kQueueSpecific;
CFStringRef queueSpecificValue = CFSTR(”queueA”)
dispatch_queue_set_specific(queueA, &kQueueSpecific, (void *)queueSpecificValue, (dispatch_function_t)CFRelease);
dispatch_sync(queueB, ^{
dispatch_blcok_t block = ^{ /*.NO DEADLOCK!*/};
CFStringRef retrievedValue = dispatch_get_specific(&kQueueSpecific);
if (retrievedValue) {
block();
}
else {
dispatch_sync(queueA, block);
}
});
要點(diǎn)
dispatch_get_current_queue函數(shù)的行為常常與開(kāi)發(fā)者所預(yù)期的不同。此函數(shù)已經(jīng)廢棄,只應(yīng)做調(diào)試之用。
由于派發(fā)隊(duì)列是按層級(jí)來(lái)組織的,所以無(wú)法單用某個(gè)隊(duì)列對(duì)象來(lái)描述“當(dāng)前隊(duì)列”這一概念。
dispatch_get_current_queue函數(shù)用于解決由不可重入的代碼所引發(fā)的死鎖,然而能用此函數(shù)解決的問(wèn)題,通常也能改用“隊(duì)列特定數(shù)據(jù)”來(lái)解決
第四十七條 熟悉系統(tǒng)框架
將一系列代碼封裝為動(dòng)態(tài)庫(kù),并在其中放入描述其接口的頭文件,這樣做出來(lái)的東西就叫框架。有時(shí)iOS平臺(tái)構(gòu)建的第三方框架所使用的是靜態(tài)庫(kù),這是因?yàn)閕OS應(yīng)用程序不允許在其中包含動(dòng)態(tài)庫(kù)。iOS系統(tǒng)框架仍然使用動(dòng)態(tài)庫(kù)。
Foundation框架中的許多功能,都可以在此框架中找到對(duì)應(yīng)的C語(yǔ)言API。
要點(diǎn)
許多系統(tǒng)框架都可以直接使用。其中最重要的是Foundation 與CoreFoundation,這兩個(gè)框架提供了構(gòu)建應(yīng)用程序所需的許多核心功能。
很多常見(jiàn)任務(wù)都能用框架來(lái)做,例如音頻與視頻處理、網(wǎng)絡(luò)通信、數(shù)據(jù)管理等
第四十八條 多用塊枚舉,少用for循環(huán)
要點(diǎn)
遍歷collection有四種方式。最基本的辦法是for循環(huán),其次是NSEnumerator遍歷法及快速遍歷法,最新、最先進(jìn)的方式則是“塊枚舉法”。
“塊枚舉法”本身就能通過(guò)GCD來(lái)并發(fā)執(zhí)行遍歷操作,無(wú)須另行編寫(xiě)代碼。而采用其他遍歷方式則無(wú)法輕易實(shí)現(xiàn)這一點(diǎn)。
若提前知道待遍歷的collection含有何種對(duì)象,則應(yīng)修改塊簽名,指出對(duì)象的具體類(lèi)型。
第四十九條 對(duì)自定義其內(nèi)存管理語(yǔ)義的collection使用無(wú)縫橋接
NSArray *anNSArray = @[@1, @2, @3, @4, @5];
CFArrayRef aCFArray = (__bridge CFArrayRef)anNSArray;
NSLog(@"Size of array = %li", CFArrayGetCount(aCFArray));
轉(zhuǎn)換操作的__bridge告訴ARC如何處理轉(zhuǎn)換所涉及的OC對(duì)象。__bridge的意思是:ARC仍然具備這個(gè)OC對(duì)象的所有權(quán)。而__bridge_retained則與之相反,意味著ARC將交出對(duì)象的所有權(quán)。若是前面那段代碼改用它來(lái)實(shí)現(xiàn),那么用完數(shù)組之后就要加上CFRelease以釋放其內(nèi)存。與之相似,反向轉(zhuǎn)換可以通過(guò)__bridge_transfer來(lái)實(shí)現(xiàn),例如,想把CFArrayRef轉(zhuǎn)換為NSArray*,并且想令A(yù)RC獲得對(duì)象所有權(quán),那么就可以采用這種轉(zhuǎn)換方式。這三種轉(zhuǎn)換方式稱(chēng)為“橋式轉(zhuǎn)換”。
要點(diǎn)
通過(guò)無(wú)縫橋接技術(shù),可以在Foundation框架中的OC對(duì)象與CoreFoundation框架中的C語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之間來(lái)回轉(zhuǎn)換。
在CoreFoundation層面創(chuàng)建collection時(shí),可以指定許多回調(diào)函數(shù),這些函數(shù)表示此collercion應(yīng)如何處理其元素。然后,可運(yùn)用無(wú)縫橋接技術(shù),將其轉(zhuǎn)換成劇本特殊內(nèi)存管理語(yǔ)義的OC collection.
第五十條 構(gòu)建緩存時(shí)選用NSCache而非NSDictionary
NSCache勝過(guò)NSDictionary之處在于,當(dāng)系統(tǒng)資源將要耗盡時(shí),它可以自動(dòng)刪減緩存。此外,NSCache還會(huì)先行刪減“最久未使用的”對(duì)象。
NSCache并不會(huì)copy鍵,而是會(huì)retain它。
原因:很多時(shí)候,鍵都時(shí)由不支持拷貝操作的對(duì)象來(lái)充當(dāng)?shù)?。所以說(shuō),在鍵不支持拷貝操作的情況下,該類(lèi)用起來(lái)必字典更方便。
另外,NSCache是線(xiàn)程安全的。在開(kāi)發(fā)者自己不編寫(xiě)加鎖代碼前提下,多個(gè)線(xiàn)程便可以同時(shí)訪(fǎng)問(wèn)NSCache.
開(kāi)發(fā)者可以操控緩存刪減其內(nèi)容的時(shí)機(jī)。其一是緩存中的對(duì)象總數(shù),其二是所有對(duì)象的“總開(kāi)銷(xiāo)”。
#import <Foundation/Foundation.h>
//Network fetcher class
typedef void (^EOCNetworkFetcherCompletionHandler)(NSData *data);
@interface EOCNetworkFetcher : NSObject
- (id)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(EOCNetworkFetcherCompletionHandler)handler;
@end
//Class that use the network fetcher and caches results
@interface EOCClass : NSobject
@end
@implementation EOCClass {
NSCache *_cache;
}
- (id)init {
if (self = [super init]) {
_cache = [NSCache new];
//Cache a maximum of 100 URLs
_cache.countLimit = 100;
//**
*The Size in bytes of data is used as the cost,
*so this set a cost limit of 5MB
*/
_cache.totalCostLimt = 5 * 1024 *1024;
}
return self;
}
- (void)downloadDataFromURL:(NSURL *)url {
NSData *cachedData = [_cache objectForKey:url];
if (cachedData) {
//Cache hit
[self useData:cachedData];
}
else {
//cache miss
EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
[fetcher startWithCompletionHandler:^(NSData *data) {
[_cache setObject:data forKey:url cost data.length];
[self useData:data];
}];
}
}
使用NSPurgeableData
- (void)downloadDataFroURL:(NSURL *)url {
NSPurgeableData *cachedData = [_cache objectForKey:url];
if (cachedData) {
//Cache hit
[cachedData beginContentAccess];
[self useData:cachedData];
[cachedData endContentAccess];
}
else {
//cache miss
EOCNetworkFetcher *fetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
[fetcher startWithCompletionHandler:^(NSData *data) {
NSPurgeableData *purgeableData = [NSPurgeableData dataWithData:data];
[_cache setObject:purgeableData forKey:url cost: purgeableData.length];
[self useData:data];
[purgeableData endContentAccess];
}];
}
}
要點(diǎn)
實(shí)現(xiàn)緩存時(shí)應(yīng)選用NSCache,因?yàn)镹SCache可以提供優(yōu)雅的自動(dòng)刪減功能,而且是“線(xiàn)程安全的”,此外,它與字典不同,不會(huì)拷貝鍵。
可以給NSCache對(duì)象設(shè)置上限,用以限制緩存中的對(duì)象總個(gè)數(shù)及“總成本”,而這些尺度則定義了緩存刪減其中對(duì)象的時(shí)機(jī)。但是絕對(duì)不要把這些尺度當(dāng)成可靠的“硬限制”,它們僅對(duì)NSCache起指導(dǎo)作用。
將NSPurgeableData與NSCache搭配使用,可實(shí)現(xiàn)自動(dòng)清除數(shù)據(jù)的功能,也就是說(shuō),當(dāng)NSPurgeableData對(duì)象所占用內(nèi)存為系統(tǒng)所丟棄時(shí),該對(duì)象也會(huì)從緩存中移除。
第五十一條 精簡(jiǎn)initialize與load的實(shí)現(xiàn)代碼
+ (void)load
對(duì)于加入運(yùn)行期系統(tǒng)中的每個(gè)類(lèi)及分類(lèi)來(lái)說(shuō),必定會(huì)調(diào)用此方法,而且僅調(diào)用一次。當(dāng)包含類(lèi)或分類(lèi)的程序庫(kù)載入系統(tǒng)時(shí),就會(huì)執(zhí)行此方法,而這通常就是指應(yīng)用程序啟動(dòng)的時(shí)候,若程序是為iOS平臺(tái)設(shè)計(jì)的,則肯定會(huì)在此時(shí)執(zhí)行。Mac OS X應(yīng)用程序更自由一些,它們可以使用“動(dòng)態(tài)加載”之類(lèi)的特性,等應(yīng)用程序啟動(dòng)好之后再去加載程序庫(kù)。如果分類(lèi)和其所屬的類(lèi)都定義了load,則先調(diào)用類(lèi)里的,再調(diào)用分類(lèi)里的。
load方法的問(wèn)題在于,執(zhí)行該方法時(shí),運(yùn)行期系統(tǒng)處于“脆弱狀態(tài)”。在執(zhí)行子類(lèi)的load方法之前,必定會(huì)先執(zhí)行所有超類(lèi)的load方法,而如果代碼還依賴(lài)其他程序庫(kù),那么程序庫(kù)里相關(guān)類(lèi)的load方法也必定會(huì)先執(zhí)行。然而,根據(jù)某個(gè)給定的程序庫(kù),卻無(wú)法判斷出其中各個(gè)類(lèi)的載入順序。因此,在load方法中使用其他類(lèi)是不安全的。
分類(lèi)和其所屬的類(lèi)里,都可能出現(xiàn)load方法。此時(shí)兩種實(shí)現(xiàn)代碼都會(huì)調(diào)用,類(lèi)的實(shí)現(xiàn)要比分類(lèi)的實(shí)現(xiàn)先執(zhí)行。
想執(zhí)行與類(lèi)相關(guān)的初始化操作,還有個(gè)辦法,就是復(fù)寫(xiě)下列方法:
+ (void)initialize
該方法會(huì)在程序首次用該類(lèi)之前調(diào)用,且只調(diào)用一次。它是由運(yùn)行期系統(tǒng)來(lái)調(diào)用的,絕不應(yīng)該通過(guò)代碼直接調(diào)用。其雖與load相似,但卻有幾個(gè)非常重要的微妙區(qū)別。
1、它是“惰性調(diào)用”,只有當(dāng)程序用到了相關(guān)的類(lèi)時(shí),才會(huì)調(diào)用。因此,如果某個(gè)類(lèi)一直都沒(méi)有使用,那么其initialize方法就一直不會(huì)運(yùn)行。也就是說(shuō),應(yīng)用程序無(wú)須先把每個(gè)類(lèi)的initialize都執(zhí)行一遍,這與load方法不同,對(duì)于load來(lái)說(shuō),應(yīng)用程序必須阻塞并等著所有類(lèi)的load都執(zhí)行完,才能繼續(xù)
2、運(yùn)行期系統(tǒng)在執(zhí)行該方法時(shí),是處于正常狀態(tài)的,此時(shí)可以安全使用并調(diào)用任意類(lèi)中的任意方法。而且,運(yùn)行期系統(tǒng)也能確保initialize方法一定會(huì)在“線(xiàn)程安全的環(huán)境”中執(zhí)行,只有執(zhí)行initialize那個(gè)線(xiàn)程可以操作類(lèi)或類(lèi)實(shí)例。其他線(xiàn)程都要先阻塞,等著initialize執(zhí)行完。
3、initialize方法與其他消息一樣,如果某個(gè)類(lèi)未實(shí)現(xiàn)它,而其超類(lèi)實(shí)現(xiàn)了,那么就會(huì)運(yùn)行超類(lèi)的實(shí)現(xiàn)代碼。
要點(diǎn)
在加載階段,如果類(lèi)實(shí)現(xiàn)了load方法,那么系統(tǒng)就會(huì)調(diào)用它。分類(lèi)里也可以定義此方法,類(lèi)的load方法要比分類(lèi)中的先調(diào)用。與其他方法不同,load方法不參與覆寫(xiě)機(jī)制。
首次使用某個(gè)類(lèi)之前,系統(tǒng)會(huì)向其發(fā)送initialize消息。由于此方法尊從普通的覆寫(xiě)規(guī)則,所以通常應(yīng)該在里面判斷當(dāng)前要初始化的是哪個(gè)類(lèi)。
load與initialize方法都應(yīng)該實(shí)現(xiàn)得精簡(jiǎn)一些,有助于保持應(yīng)用程序的響應(yīng)能力,也能減少引入“依賴(lài)環(huán)”的幾率。
無(wú)法在編譯期設(shè)定的全局常量,可以放在initialize方法里初始化。
第五十二條 別忘了NSTimer會(huì)保留其目標(biāo)對(duì)象
計(jì)時(shí)器會(huì)保留其目標(biāo)對(duì)象,等到自身“失效”時(shí)再釋放此對(duì)象。調(diào)用invalidate方法可令計(jì)時(shí)器失效;執(zhí)行完相關(guān)任務(wù)后,一次性的計(jì)時(shí)器也會(huì)失效。
要點(diǎn)
NSTimer對(duì)象會(huì)保留其目標(biāo),直到計(jì)時(shí)器本身失效為止,調(diào)用invalidate方法可令計(jì)時(shí)器失效,另外,一次性的計(jì)時(shí)器在觸發(fā)完任務(wù)之后也會(huì)失效。
反復(fù)執(zhí)行任務(wù)的計(jì)時(shí)器,很容易引起保留環(huán),如果這種計(jì)時(shí)器的目標(biāo)對(duì)象又保留了計(jì)時(shí)器本身,那肯定會(huì)導(dǎo)致保留環(huán)。這種環(huán)狀保留關(guān)系,可能是直接發(fā)生的,也可能是通過(guò)對(duì)象圖里的其他對(duì)象間接發(fā)生的。
可以擴(kuò)充N(xiāo)STimer的功能,用“塊”來(lái)打破保留環(huán)。不過(guò),除非NSTimer將來(lái)在公共接口里提供此功能,否則必須創(chuàng)建分類(lèi),將相關(guān)實(shí)現(xiàn)代碼加入其中。