1 了解 Objective-C 起源
Objective-C 使用“消息結(jié)構(gòu)”而非“函數(shù)調(diào)用”瞬浓。
使用“消息結(jié)構(gòu)”的語(yǔ)言,其運(yùn)行時(shí)所執(zhí)行的代碼由運(yùn)行環(huán)境來(lái)決定蓬坡。
使用“函數(shù)調(diào)用”的語(yǔ)言猿棉,則由編譯器決定。
分配在堆內(nèi)存必須直接管理屑咳,而分配在棧上用于保存變量的內(nèi)存則會(huì)在棧幀彈出時(shí)自動(dòng)清理萨赁。
Objective-C 將堆內(nèi)存管理抽象出來(lái)了,不需要 malloc 及 free 來(lái)分配或釋放對(duì)象所占內(nèi)存兆龙。
這部分工作抽象為一套內(nèi)存管理架構(gòu):引用計(jì)數(shù)杖爽。
2 在類的頭文件中盡量少引入其他頭文件
建議:
- “向前聲明”該類
@class XXClass
- 若無(wú)法使用“向前聲明”,盡量將其挪到實(shí)現(xiàn)文件當(dāng)中详瑞。
3 多用字面量語(yǔ)法
即使用語(yǔ)法糖掂林,常用在創(chuàng)建字符串、數(shù)組坝橡、字典泻帮。
當(dāng)使用語(yǔ)法糖,創(chuàng)建數(shù)組计寇、字典時(shí)锣杂,要避免值中有 nil,否則會(huì)有異常番宁。
4 多用類型常量元莫,少用 #define 預(yù)處理指令
不要用預(yù)處理指令定義常量,因?yàn)檫@樣出來(lái)的常量蝶押,沒(méi)有類型信息踱蠢。如果有人重新定義了常量值,編譯器也不會(huì)報(bào)警棋电。
在實(shí)現(xiàn)文件中茎截,使用 static const 來(lái)定義“只在編譯單元內(nèi)可見(jiàn)的常量”(translation-unit-specific constant),一般以 k 開(kāi)頭赶盔。
在頭文件中用 extern 聲明全局變量靶端,并在實(shí)現(xiàn)文件中定義其值粪糙,這樣的常量會(huì)出現(xiàn)在全局符號(hào)表中死相,通常以類型為前綴蒸苇。
5 用枚舉表示狀態(tài)陡鹃、選項(xiàng)、狀態(tài)碼
主要使用 NS_ENUM 和 NS_OPTIONS 來(lái)定義枚舉類型抖坪。
在處理枚舉類型的 switch 語(yǔ)句中不要實(shí)現(xiàn) default萍鲸,這樣添加類型時(shí),就會(huì)收到編譯器警告擦俐。
6 理解“屬性”這一概念
可以使用 @property 來(lái)定義對(duì)象中所封裝的數(shù)據(jù)猿推。
通過(guò)“特質(zhì)”來(lái)指定存儲(chǔ)數(shù)據(jù)所需要的語(yǔ)義,特質(zhì)有4種:
- 原子性
- 讀寫(xiě)權(quán)限
- 內(nèi)存管理語(yǔ)義
- 方法名
在設(shè)置屬性所對(duì)應(yīng)的實(shí)例變量時(shí)捌肴,一定要遵從該屬性所聲明的語(yǔ)義。
7 在對(duì)象內(nèi)部盡量直接訪問(wèn)實(shí)例變量
在對(duì)象內(nèi)部藕咏,讀取實(shí)例變量時(shí)状知,除非是“懶加載”,否則盡量直接訪問(wèn)實(shí)例變量孽查,若是寫(xiě)入數(shù)據(jù)饥悴,應(yīng)通過(guò)屬性來(lái)寫(xiě)。
在初始化以及 dealloc 方法中盲再,應(yīng)該直接訪問(wèn)實(shí)例變量西设。
除了此時(shí)實(shí)例不穩(wěn)定外,有可能子類會(huì)重寫(xiě) Setter 方法答朋,這樣會(huì)拋出異常贷揽。
8 理解“對(duì)象等同性”
若想檢測(cè)對(duì)象的等同性,需要提供 isEqual 和 hash 方法梦碗。
相同的對(duì)象具有相同的 hash 碼禽绪,但2個(gè)相同 hash 碼的對(duì)象未必相同。
編寫(xiě) hash 方法時(shí)洪规,應(yīng)使用計(jì)算速度快且 hash 碰撞率低的算法印屁。
9 以“類族”模式隱藏實(shí)現(xiàn)細(xì)節(jié)
常見(jiàn)的有 NSString。
可從類族的公共抽象基類中繼承子類斩例,但要格外注意雄人,若有開(kāi)發(fā)文檔,應(yīng)先仔細(xì)閱讀文檔念赶。
10 在既有類中使用關(guān)聯(lián)對(duì)象存放自定義數(shù)據(jù)
當(dāng)需要給某個(gè)對(duì)象存放數(shù)據(jù)础钠,一般是繼承該類,然后改用子類晶乔。
但有些時(shí)候珍坊,類是由某種特殊機(jī)制產(chǎn)生的,開(kāi)發(fā)者無(wú)法使用這種機(jī)制創(chuàng)建子類正罢。
這時(shí)阵漏,可以使用關(guān)聯(lián)對(duì)象方式驻民,關(guān)鍵方法:
// 根據(jù)給定的鍵和策略為某對(duì)象設(shè)置關(guān)聯(lián)對(duì)象值
void objc_setAssociatedObject(id object, void*key, id value, objc_AssociationPolicy)
// 根據(jù) key 從某對(duì)象中獲取相應(yīng)的關(guān)聯(lián)對(duì)象值
id objc_getAssociatedObject(id object, void*key)
// 移除對(duì)象的所有關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(id object)
通常使用靜態(tài)全局變量作為 key。
關(guān)聯(lián)類型:
關(guān)聯(lián)類型 | 等效的 @property 屬性 |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic, retain |
OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic, retain |
OBJC_ASSOCIATION_RETAIN | retain |
OBJC_ASSOCIATION_COPY | copy |
只有在其他方式無(wú)法實(shí)現(xiàn)時(shí)履怯,才考慮使用關(guān)聯(lián)對(duì)象回还,因?yàn)樗赡軙?huì)引用難以發(fā)現(xiàn)的 bug。
11 理解 objc_msgSend 的作用
對(duì)象調(diào)用方法叹洲,在 Objective-C 上稱為方法調(diào)用(pass a message)柠硕。
“動(dòng)態(tài)消息派發(fā)系統(tǒng)”(dynamic message dispatch system)會(huì)查找對(duì)應(yīng)方法,并執(zhí)行相應(yīng)代碼运提。
原型如下:
void objc_msgSend(id self, SEL cmd, ...)
objc_msgSend 會(huì)根據(jù)接收者與選擇子的類型來(lái)調(diào)用適當(dāng)方法蝗柔,它會(huì)在接收者的方法列表(list of methods)中尋找與選擇子名稱相符的方法,如果找不到民泵,就執(zhí)行“消息轉(zhuǎn)發(fā)”(message forwarding)癣丧。
有些“邊界情況(edge case)”需要交由 Objective-C 運(yùn)行環(huán)境中的另外一些函數(shù)來(lái)處理:
- objc_msgSend_stret
如果待發(fā)送消息要返回結(jié)構(gòu)體,那么可交由此函數(shù)處理栈妆。
只有當(dāng) CPU 的寄存器能容納消息返回類型時(shí)胁编,該函數(shù)才能處理此信息。
若是無(wú)法容納鳞尔,那么由另外一個(gè)函數(shù)執(zhí)行派發(fā)嬉橙,此時(shí),那個(gè)函數(shù)會(huì)通過(guò)分配在棧上的某個(gè)變量來(lái)處理消息所返回的結(jié)構(gòu)體寥假。 - objc_msgSend_fpret
如果消息返回的是浮點(diǎn)數(shù)市框,可交由此函數(shù)處理。
在某些架構(gòu)的 CPU 上調(diào)用函數(shù)時(shí)昧旨,需要對(duì)“浮點(diǎn)數(shù)寄存器”做特殊處理拾给。 - objc_msgSendSuper
如果是給超類發(fā)消息,可交由此函數(shù)處理兔沃。
也有與上述2個(gè)函數(shù)等效的方法蒋得,用于處理發(fā)給 super 的相應(yīng)消息。
objc_msgSend 等函數(shù)一旦找到應(yīng)該調(diào)用的方法實(shí)現(xiàn)后乒疏,就會(huì)“跳轉(zhuǎn)過(guò)去”额衙,之所以能這樣,是因?yàn)?Objective-C 對(duì)象的每個(gè)方法都可以視為簡(jiǎn)單的 C 函數(shù)怕吴,原型如下:
<return type> Class_seletor(id self, SEL _cmd, ...)
每個(gè)類里都有一張表格窍侧,其中的指針指向這種函數(shù),而選擇子的名稱正是查表時(shí)所用的“鍵”转绷。
而且原型的樣子和 objc_msgSend 函數(shù)很像伟件,是為了利用“尾調(diào)用優(yōu)化”技術(shù)。
結(jié)果緩存在快速映射表(fast map)中议经,每個(gè)類都有這樣一塊內(nèi)存斧账,若是稍后還向該類發(fā)送同樣信息谴返,執(zhí)行起來(lái)就會(huì)很快。
12 理解消息轉(zhuǎn)發(fā)機(jī)制
當(dāng)對(duì)象接收到無(wú)法解讀的消息后咧织,就會(huì)啟動(dòng)“消息轉(zhuǎn)發(fā)”機(jī)制嗓袱。
分為2個(gè)階段:
- 動(dòng)態(tài)方法解析(dynamic method resolution)
先征詢接收者所屬的類习绢,看其是否能動(dòng)態(tài)添加方法渠抹,以處理當(dāng)前這個(gè)“未知的選擇子”。 - 涉及完整的消息轉(zhuǎn)發(fā)機(jī)制(full forwarding mechanism)闪萄。
首先梧却,請(qǐng)接收者看看有沒(méi)有其他對(duì)象能處理這條消息。若有败去,則 runtime 會(huì)把消息轉(zhuǎn)給那個(gè)對(duì)象篮幢,于是消息轉(zhuǎn)發(fā)過(guò)程結(jié)束。
若沒(méi)有“備援的接收者”(replacemoent receiver)为迈,則啟動(dòng)完整的消息轉(zhuǎn)發(fā)機(jī)制,runtime 系統(tǒng)會(huì)把與消息有關(guān)的全部細(xì)節(jié)都封裝到 NSInvocation 對(duì)象中缺菌,讓接收者設(shè)法解決當(dāng)前未處理的這條消息葫辐。
動(dòng)態(tài)方法解析
// 對(duì)象在收到無(wú)法解讀的消息后,調(diào)用下列方法
+ (BOOL)resolveInstanceMethod:(SEL)selector;
// 若未實(shí)現(xiàn)的是類方法伴郁,則調(diào)用以下方法:
+ (BOOL)resolveClassMethod:(SEL)selector;
使用這種方法的前提耿战,相關(guān)方法的實(shí)現(xiàn)代碼已經(jīng)寫(xiě)好,只等著運(yùn)行的時(shí)候動(dòng)態(tài)插在類里面即可焊傅。
此方案常用來(lái)實(shí)現(xiàn) @dynamic 屬性剂陡。
備援接收者
runtime 詢問(wèn)是否還有別的接收者來(lái)處理這條消息,對(duì)應(yīng)的處理方法:
- (id)forwardingTargetForSelector:(SEL)selector;
若當(dāng)前接收者能找到備援對(duì)象狐胎,就將其返回鸭栖,若找不到,就返回 nil握巢。
通過(guò)此方案晕鹊,我們可用“組合(composition)”來(lái)模擬出“多重繼承”的某些特性。
完整的消息轉(zhuǎn)發(fā)
創(chuàng)建 NSInvocation 對(duì)象暴浦,把尚未處理的那條消息相關(guān)的全部細(xì)節(jié)封于其中:選擇子溅话、目標(biāo)(target)及參數(shù)。
在觸發(fā) NSInvocation 對(duì)象時(shí)歌焦,消息派發(fā)系統(tǒng)(message-dispatch system)把消息指派給目標(biāo)對(duì)象飞几。
調(diào)用以下方法:
- (void)forwardInvocation:(NSInvocation *)invocation;
較好的實(shí)現(xiàn)方式:
在觸發(fā)消息前,先以某種方式改變消息內(nèi)容独撇,比如追加另外一個(gè)參數(shù)屑墨,或是改換選擇子躁锁。
實(shí)現(xiàn)此方法時(shí),若發(fā)現(xiàn)某調(diào)用操作不應(yīng)由本類處理绪钥,則需調(diào)用超類的同名方法灿里。這樣,繼承體系中的每個(gè)類都有機(jī)會(huì)處理此調(diào)用請(qǐng)求程腹,直到 NSObject匣吊。
如果最后調(diào)用了 NSObject 類的方法,那么該方法還會(huì)繼而調(diào)用 doesNotRecognizeSelector:
以拋出異常寸潦,此異常表明最終未能處理色鸳。
13 用“方法調(diào)配技術(shù)”調(diào)試黑盒方法
核心方法如下,用來(lái)交換兩個(gè)方法的實(shí)現(xiàn)见转。
void method_exchangeImplementations(Method m1, Method m2)
在運(yùn)行期命雀,可向類中新增或替換選擇子所對(duì)應(yīng)的方法實(shí)現(xiàn)。
一般只在調(diào)試時(shí)使用斩箫,不宜濫用這種方法吏砂。
14 理解“類對(duì)象”含義
從開(kāi)源的 objc4-723 中可以找到下列聲明
// objc.h
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
// runtime.h
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
如上所述,每個(gè)實(shí)例都有一個(gè)指向 Class 對(duì)象的指針乘客,用以表明類型狐血,而這些 Class 對(duì)象則構(gòu)成類的繼承體系。
類型信息查詢方法:isKindOfClass
, isMemberOfClass
易核。
盡量使用類型信息查詢方法確定對(duì)象類型匈织,不要直接比較類對(duì)象,因?yàn)橛行╊悓?duì)象可能實(shí)現(xiàn)了消息轉(zhuǎn)發(fā)功能牡直。
15 用前綴避免命名空間沖突
使用 Objective-C 的常識(shí)缀匕,不再贅述。
16 提供“全能初始化方法”
即“指定初始化方法”碰逸。
若此方法與超類的不同乡小,則需覆寫(xiě)超類中的對(duì)應(yīng)方法。
若超類的初始化方法不適用于子類饵史,則應(yīng)覆寫(xiě)這個(gè)超類方法劲件,并在其中拋出異常。
17 實(shí)現(xiàn) description 方法
自定義某個(gè)對(duì)象的打印信息约急。
若想在(使用 lldb)調(diào)試時(shí)打印出更詳細(xì)信息零远,則應(yīng)實(shí)現(xiàn) debugDescription 方法。
18 盡量使用不可變對(duì)象
盡量創(chuàng)建不可變對(duì)象厌蔽。
若某屬性僅可用于對(duì)象內(nèi)部修改牵辣,則在“class-continuation 分類”中將其由 readonly 屬性擴(kuò)展為 readwrite 屬性。
不要把可變的 collection 作為屬性公開(kāi)奴饮,而應(yīng)提供相關(guān)方法來(lái)修改纬向。
19 使用清晰而協(xié)調(diào)的命名方式
基本常識(shí)择浊,不再贅述。
20 為私有方法加前綴
不要單用一個(gè)下劃線做私有方法的前綴逾条,因?yàn)檫@種方法是 Apple 官方使用的琢岩。
可考慮使用 p_
作為前綴。
21 理解 Objective-C 的錯(cuò)誤模型
ARC 在默認(rèn)情況下不是“異常安全的”师脂,即若拋出異常担孔,那么本應(yīng)在作用域末尾釋放的對(duì)象,現(xiàn)在卻不會(huì)自動(dòng)釋放了吃警。
若想生成“異常安全”的代碼糕篇,可通過(guò)打開(kāi)編譯器的標(biāo)志:-fobjc-arc-exceptions
實(shí)現(xiàn),并且引入一些額外代碼酌心。
異常只用于極嚴(yán)重的錯(cuò)誤拌消,其他錯(cuò)誤返回 nil/0 或使用 NSError。
使用 NSError安券,一般有2種方式:
- 指派“委托方法”墩崩。
- 把錯(cuò)誤信息放在 NSError 中,經(jīng)由“輸出參數(shù)”返回給調(diào)用者侯勉。
22 理解 NSCopying 協(xié)議
若想類支持拷貝操作泰鸡,就要實(shí)現(xiàn) NSCopying 協(xié)議:
- (id)copyWithZone:(NSZone *)zone
出現(xiàn) NSZone 的原因是:以前開(kāi)發(fā)程序時(shí),會(huì)據(jù)此把內(nèi)存分成不同的區(qū)壳鹤,而對(duì)象會(huì)創(chuàng)建在某個(gè)區(qū)里。現(xiàn)在只有一個(gè)默認(rèn)區(qū)饰迹,所以不必?fù)?dān)心其中的 zone 參數(shù)芳誓。
若是對(duì)象還有可變版本,則需要同時(shí)實(shí)現(xiàn) NSCopying 與 NSMutableCopying 協(xié)議啊鸭。
-[NSMutableArray copy] => NSArray
-[NSArray mutableCopy] => NSMutableArray
在可變對(duì)象上調(diào)用 copy 方法會(huì)返回另外一個(gè)不可變類的實(shí)例锹淌。
Foundation 中的所有 collection 類在默認(rèn)情況下都執(zhí)行淺拷貝,因?yàn)槿萜鲀?nèi)的對(duì)象未必都能拷貝赠制,而且調(diào)用者也未必想一并拷貝容器內(nèi)對(duì)象赂摆。
復(fù)制對(duì)象時(shí)需要決定采用淺拷貝還是深拷貝,一般情況下盡量執(zhí)行淺拷貝钟些。
如果所寫(xiě)對(duì)象需深拷貝烟号,那么可考慮新增一個(gè)專門(mén)執(zhí)行深拷貝的方法。
23 通過(guò) delegate 和 datasource 協(xié)議進(jìn)行對(duì)象間通信
為了避免“循環(huán)引用”政恍,delegate 屬性要定義為 weak汪拥。
datasource 同樣是協(xié)議,主要用來(lái)從另外一個(gè)對(duì)象獲取數(shù)據(jù)篙耗。
使用 delegate迫筑,需要每次都使用 respondToSelector:
來(lái)檢查對(duì)象是否可響應(yīng)選擇子宪赶。
如果需要頻繁檢測(cè),倒不如把是否能響應(yīng)某個(gè)選擇子的結(jié)果緩存起來(lái)脯燃,將結(jié)果緩存起來(lái)的最佳途徑:使用 bifield
數(shù)據(jù)類型:
// 數(shù)字代表位數(shù)搂妻,比如 fieldA 可以代表0-255之間的值
struct data {
unsigned int fieldA : 8;
unsigned int fieldB : 4;
}
如果只是緩存能否響應(yīng),那么只需要1位就可以存儲(chǔ)結(jié)果辕棚。
24 將類的實(shí)現(xiàn)代碼欲主,分配到數(shù)個(gè)分類中,以便于管理
除了分成多個(gè)易于管理的小塊外坟募,也可以隱藏實(shí)現(xiàn)細(xì)節(jié):將應(yīng)該視為“私有”的方法歸入名為 Private 的分類中岛蚤。
25 總是為第三方類的分類名稱加前綴
除了名稱外,還有方法名懈糯,目標(biāo)都是為了不與其他庫(kù)發(fā)生沖突涤妒。
26 勿在分類中聲明屬性
雖然在技術(shù)上可以使用關(guān)聯(lián)對(duì)象實(shí)現(xiàn),但不建議這樣做赚哗,原因:
- 會(huì)有很多重復(fù)代碼她紫。
- 在內(nèi)存管理問(wèn)題上容易出錯(cuò),因?yàn)樵跒閷傩詫?shí)現(xiàn)存取方法時(shí)屿储,經(jīng)常忘記遵從其內(nèi)存管理語(yǔ)義贿讹。
27 使用“class-continuation 分類”隱藏實(shí)現(xiàn)細(xì)節(jié)
即常說(shuō)的擴(kuò)展。用法:
- 可用它向類中新增實(shí)例變量够掠。
- 如果某個(gè)屬性在主接口中聲明為
readonly
民褂,那么可在類的內(nèi)存聲明擴(kuò)展,然后再將屬性聲明為readwrite
疯潭。 - 用來(lái)聲明私有方法的原型赊堪,雖然新版編譯器不強(qiáng)制使用方法前必須先聲明。
- 如果希望所遵循的協(xié)議不為人所知竖哩,也可在其中聲明哭廉。
28 通過(guò)協(xié)議提供匿名對(duì)象
可在某種程度上提供匿名對(duì)象,具體的對(duì)象類型相叁,可以淡化成只要遵從某協(xié)議的 id 類型遵绰,協(xié)議里規(guī)定了對(duì)象需要實(shí)現(xiàn)的方法。
如 NSMutableDictionary
- (void)setObject:(id)object forKey:(id<NSCopying>)key
29 理解引用計(jì)數(shù)
30 以 ARC 簡(jiǎn)化引用計(jì)數(shù)
ARC 回收 Objective-C++ 對(duì)象時(shí)增淹,待回收對(duì)象會(huì)調(diào)用所有 C++ 對(duì)象的析構(gòu)函數(shù)椿访。
31 在 dealloc 方法中只釋放引用并解除監(jiān)聽(tīng)
編譯器如果發(fā)現(xiàn)對(duì)象里有 C++ 對(duì)象,就會(huì)生成名為:.cxx_destruct
的方法虑润。
32 編寫(xiě)“異常安全代碼”時(shí)留意內(nèi)存管理問(wèn)題
33 以弱引用避免保留環(huán)
34 以“自動(dòng)釋放池塊”降低內(nèi)存峰值
35 用“僵尸對(duì)象”調(diào)試內(nèi)存管理問(wèn)題
通過(guò)環(huán)境變量 NSZombieEnabled
可開(kāi)啟功能赎离。
_NSZombie_
未實(shí)現(xiàn)任何方法,而且是個(gè)根類,只有一個(gè)實(shí)例變量 isa
梁剔,根據(jù)消息轉(zhuǎn)發(fā)的規(guī)則虽画,發(fā)給它的全部消息都要經(jīng)過(guò)“完整的消息轉(zhuǎn)發(fā)機(jī)制”。
系統(tǒng)會(huì)給每個(gè)變?yōu)榻┦念悇?chuàng)建一個(gè)對(duì)應(yīng)的新類荣病,它會(huì)把整個(gè) _NSZombie_
類結(jié)構(gòu)拷貝一份码撰,并賦予新的名字。因?yàn)槿绻阉薪┦瑢?duì)象都?xì)w到 _NSZombie_
類里个盆,那么對(duì)象原來(lái)的類信息就會(huì)丟失脖岛。
系統(tǒng)會(huì)修改對(duì)象的 isa 指針,令其指向特殊的僵尸類颊亮,從而使該對(duì)象變?yōu)榻┦瑢?duì)象柴梆。
僵尸對(duì)象能響應(yīng)所有的選擇子:打印一條消息內(nèi)容及其接收者的信息,然后終止應(yīng)用程序终惑。
36 不要使用 retain count
從 29 到 36 多為內(nèi)存管理相關(guān)绍在,可看
《Objective-C 內(nèi)存管理》
《Objective-C自動(dòng)引用計(jì)數(shù)ARC》
37 理解 block
在聲明它的范圍內(nèi),所有變量都可以為其捕獲雹有,但默認(rèn)情況下偿渡,被它捕獲的變量,是不可以在 block
里修改的霸奕。
若是要修改溜宽,得添加 __block
修飾符。
block 會(huì)把捕獲的所有(指針)變量都拷貝一份质帅,放在其結(jié)構(gòu)中适揉。
類型
- NSStackBlock 棧
定義 block 時(shí),其所占內(nèi)存是分配在棧中煤惩。 - NSMallocBlock 堆
如果給某個(gè) block 發(fā)送 copy 消息嫉嘀,就可以將其拷貝到堆上,這樣它就成為一個(gè)有引用計(jì)數(shù)的對(duì)象盟庞。 - NSGlobalBlock 全局
像這樣的 block,在編譯時(shí)期就確定了所需的全部信息汤善,那么它就作為一個(gè)全局 block什猖。void (^myBlock)() = ^{ NSLog("It is a block"); }
38 為常用的 block 類型創(chuàng)建 typedef
39 用 handler block 降低代碼分散程度
40 block 引用其所屬對(duì)象時(shí),要避免出現(xiàn)保留環(huán)
38 - 40 較常見(jiàn)红淡,不再贅述不狮。
41 多用派發(fā)隊(duì)列,少用同步鎖
- 同步鎖在旱,在極端情況下會(huì)導(dǎo)致死鎖
@synchronized(id) {
// TODO
}
頻繁使用同步鎖摇零,會(huì)降低代碼效率,因?yàn)楣灿猛粋€(gè)鎖的那些同步塊桶蝎,都必須按照順序執(zhí)行驻仅。
- 使用 NSLock 對(duì)象
_lock = [[NSLock alloc] init];
[_lock lock];
...
[_lock unlock];
使用 NSRecursiveLock(遞歸鎖)谅畅,線程能多次持有該鎖,而不會(huì)出現(xiàn)死鎖現(xiàn)象噪服。
替代方案:GCD
// 異步隊(duì)列
dispatch_async(_syncQueue, ^{
// TODO:
});
如果希望隊(duì)列單獨(dú)執(zhí)行毡泻,可用:
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
42 多用 GCD,少用 performSelector 系列方法
performSelector 系列方法在內(nèi)存管理方面容易有疏忽粘优,它無(wú)法確定將要執(zhí)行的選擇子具體是什么仇味,所以 ARC 無(wú)法添加合適的內(nèi)存管理方法。
performSelector 系列方法所能處理的選擇子過(guò)于局限:返回值類型及發(fā)送參數(shù)個(gè)數(shù)都有限制雹顺。
如果想把任務(wù)放在另外一個(gè)線程上執(zhí)行丹墨,更應(yīng)該使用 GCD 的相關(guān)方法來(lái)實(shí)現(xiàn)。
43 掌握 GCD 及操作隊(duì)列的使用時(shí)機(jī)
NSOperation 提供一套高層的 Objective-C API嬉愧,以實(shí)現(xiàn)純 GCD 所具備的大部分功能贩挣,且能完成一些更為復(fù)雜的操作。
44 通過(guò) Dispatch Group 機(jī)制英染,根據(jù)系統(tǒng)資源狀況來(lái)執(zhí)行任務(wù)
一系列任務(wù)可歸入一個(gè) dispatch group 中揽惹,開(kāi)發(fā)者可在這組任務(wù)執(zhí)行完畢時(shí)獲得通知。
通過(guò) dispatch group四康,可以在并發(fā)式派發(fā)隊(duì)列里同時(shí)執(zhí)行多項(xiàng)任務(wù)搪搏,此時(shí) GCD 會(huì)根據(jù)系統(tǒng)資源狀況來(lái)調(diào)度這些并發(fā)執(zhí)行的任務(wù)。
45 使用 dispatch_once 來(lái)執(zhí)行只需執(zhí)行一次的線程安全代碼
常見(jiàn)的是單例實(shí)現(xiàn)
+ (id)sharedInstance {
static id instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
標(biāo)記應(yīng)該聲明在 static 或 global 作用域內(nèi)闪金,這樣疯溺,在把只需執(zhí)行一次的 block 交付給 dispatch_once 函數(shù)時(shí),傳進(jìn)去的標(biāo)記也是相同的哎垦。
46 不要使用 dispatch_get_current_queue
此函數(shù)的行為常常與開(kāi)發(fā)者所預(yù)期的不同囱嫩,目前已廢棄,只做調(diào)試使用漏设。
由于派發(fā)隊(duì)列是按層級(jí)來(lái)組織的墨闲,所以無(wú)法單用某個(gè)隊(duì)列對(duì)象來(lái)描述“當(dāng)前隊(duì)列”。
此函數(shù)用于解決由“不可重入”的代碼所引發(fā)的死鎖郑口,然而通用此函數(shù)解決的問(wèn)題鸳碧,通常也能改用“隊(duì)列特定數(shù)據(jù)”解決。
47 熟悉系統(tǒng)框架
基礎(chǔ)知識(shí)犬性,不再贅述瞻离。
48 多用塊枚舉,少用 :qfor 循環(huán)
遍歷 collection 有4種方式:
- for 循環(huán)
- NSEnumerator 遍歷
- 快速遍歷:for in
- "塊枚舉法"
塊枚舉法本身能通過(guò) GCD 來(lái)并發(fā)執(zhí)行遍歷操作
若提前知道待遍歷的 collection 有何種對(duì)象乒裆,應(yīng)修改塊簽名套利,指出對(duì)象具體類型。
49 對(duì)自定義其內(nèi)存管理語(yǔ)義的 Collection 使用無(wú)縫橋接
使用無(wú)縫橋接技術(shù),可以在 Foudation 框架中的 Objective-C 對(duì)象與 CoreFoundation 框架中的 C 語(yǔ)言數(shù)據(jù)結(jié)構(gòu)之間來(lái)回轉(zhuǎn)換肉迫。
在 CoreFoundation 層面創(chuàng)建 collection 時(shí)验辞,可以指定許多回調(diào)函數(shù),這些函數(shù)表示此 collollection 應(yīng)如何處理其元素昂拂,然后受神,借助無(wú)縫橋接技術(shù),將其轉(zhuǎn)換成具備特殊內(nèi)存管理語(yǔ)義的 Objective-C collection格侯。
50 構(gòu)建緩存時(shí)使用 NSCache 而不是 NSDictionary
原因:
當(dāng)系統(tǒng)資源將要耗盡時(shí)鼻听,NSCache 可以自動(dòng)刪減內(nèi)存,而 NSDictionary 需要自己編寫(xiě)處理:在系統(tǒng)發(fā)出 Low Memory 通知時(shí)手工刪減內(nèi)存联四。
且 NSCache 作為 Foundation 的一部分撑碴,它能在更深層面進(jìn)行處理。
NSCache 會(huì)先行刪減“最久未使用的”對(duì)象朝墩。NSCache 不會(huì)『拷貝』鍵醉拓,而是保留它。
原因:很多時(shí)候收苏,鍵都是由不支持拷貝操作的對(duì)象來(lái)充當(dāng)亿卤。NSCache 是線程安全的,而 NSDictionary 則絕對(duì)不具備這優(yōu)勢(shì)鹿霸。
可以給 NSCache 對(duì)象設(shè)置上限排吴,用以控制緩存
有2個(gè)與系統(tǒng)資源相關(guān)的尺度可供調(diào)整:
- 是緩存中的對(duì)象總數(shù)。
- 所有對(duì)象的“總開(kāi)銷”懦鼠。
對(duì)象在加入緩存時(shí)钻哩,可為其指定“開(kāi)銷值”。
在可用資源緊張的時(shí)候肛冶,可能會(huì)刪減某個(gè)對(duì)象街氢,所以通過(guò)調(diào)整“開(kāi)銷值”來(lái)迫使緩存優(yōu)先刪除某對(duì)象,不是個(gè)好主意睦袖。
只有在能很快計(jì)算出“開(kāi)銷值”的情況下珊肃,才應(yīng)該考慮這個(gè)尺度,比如說(shuō) NSData 對(duì)象馅笙,可把數(shù)據(jù)大小作為“開(kāi)銷值”來(lái)使用伦乔,因其數(shù)據(jù)大小是已知的。
NSPurgeableData 與 NSCache 搭配使用延蟹,可實(shí)現(xiàn)自動(dòng)清除數(shù)據(jù)的功能评矩。
NSPurgeableData 是 NSMutableData 的子類叶堆,而且實(shí)現(xiàn)了 NSDiscardableContent 協(xié)議阱飘。
當(dāng) NSPurgeableData 對(duì)象所占內(nèi)存被系統(tǒng)丟棄時(shí),該對(duì)象自身也會(huì)從緩存中移除。
只有那些“重新計(jì)算起來(lái)很費(fèi)事的”數(shù)據(jù)沥匈,才值得被放入緩存
如從網(wǎng)絡(luò)獲取或磁盤(pán)讀取的數(shù)據(jù)蔗喂。
51 精簡(jiǎn) initialize 與 load 的實(shí)現(xiàn)代碼
執(zhí)行 load 時(shí),運(yùn)行期系統(tǒng)處于“脆弱狀態(tài)”(fragile state)
在執(zhí)行子類的 load 方法之前高帖,必定會(huì)先執(zhí)行所有超類的 load 方法缰儿,如果代碼依賴了其他程序庫(kù),那么程序庫(kù)里相關(guān)類的 load 方法散址,也會(huì)被執(zhí)行乖阵。
然而,根據(jù)某個(gè)給定的程序庫(kù)预麸,卻無(wú)法判斷其中各個(gè)類的加載順序瞪浸。
所以,在 load 方法中吏祸,使用其他類是不安全的对蒲。
關(guān)于 load 的繼承規(guī)則:
- 如果某個(gè)類不實(shí)現(xiàn) load 方法,無(wú)論超類是否有實(shí)現(xiàn) load 方法贡翘,都不會(huì)調(diào)用蹈矮。
- 分類和類都有可能出現(xiàn) load 方法,若是有這種情況鸣驱,系統(tǒng)先調(diào)用類中的泛鸟,再調(diào)用分類的。
但現(xiàn)在已經(jīng)很少使用 load 了丐巫,若是有使用谈况,load 中的代碼需要盡量精簡(jiǎn)。
initialize VS load
- initialize 是惰性調(diào)用的递胧,只有程序用到相關(guān)類時(shí)碑韵,才會(huì)調(diào)用,但對(duì) load 來(lái)說(shuō)缎脾,應(yīng)用程序必須阻塞并等著所有類的 load 都執(zhí)行完祝闻,才能繼續(xù)。
- 運(yùn)行期系統(tǒng)在執(zhí)行 initialize 時(shí)遗菠,是處于正常狀態(tài)的联喘,所以可以安全使用并調(diào)用任意類中的任意方法,而且運(yùn)行期系統(tǒng)也會(huì)確保 initialize 是線程安全的辙纬,即只有執(zhí)行 initialize 的線程可以操作類或其實(shí)例豁遭,其他線程會(huì)先阻塞,等待 initialize 執(zhí)行完贺拣。
- 關(guān)于繼承規(guī)則蓖谢,initialize 跟其他消息一樣捂蕴,即使沒(méi)有實(shí)現(xiàn)它,而其超類實(shí)現(xiàn)了闪幽,就會(huì)調(diào)用超類的實(shí)現(xiàn)代碼啥辨。
精簡(jiǎn) initialize 的原因:
- 大家不希望程序掛起。
對(duì)于某個(gè)類來(lái)說(shuō)盯腌,任何線程都可能成為初次用到它的那個(gè)線程溉知,并導(dǎo)致其阻塞,如果那個(gè)線程碰巧是 UI 線程腕够,就會(huì)導(dǎo)致程序無(wú)響應(yīng)级乍。 - 開(kāi)發(fā)者無(wú)法控制類的初始化時(shí)機(jī)。
運(yùn)行期系統(tǒng)更新后帚湘,也有可能會(huì)修改類的初始化方式卡者。 - 如果代碼很復(fù)雜,可能會(huì)用到其他類客们,系統(tǒng)會(huì)迫使其他類初始化崇决。
然而,本類的初始化方法此時(shí)尚未運(yùn)行完畢底挫,其他類在執(zhí)行 initialize 時(shí)恒傻,也有可能會(huì)用到本類的某些數(shù)據(jù),而這些數(shù)據(jù)可能還未初始化好建邓。
若某個(gè)全局狀態(tài)變量無(wú)法在編譯時(shí)期初始化盈厘,那么可以將它放到 initialize 來(lái)做,比如說(shuō)全局 NSArray 對(duì)象官边。
52 別忘了 NSTimer 會(huì)保留其目標(biāo)對(duì)象
反復(fù)執(zhí)行任務(wù)的計(jì)時(shí)器沸手,很容易造成循環(huán)引用。
可以給 NSTimer 添加 Block 來(lái)打破循環(huán)引用注簿。