《Effective Objective-C 2.0》筆記

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 在類的頭文件中盡量少引入其他頭文件

建議:

  1. “向前聲明”該類 @class XXClass
  2. 若無(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è)階段:

  1. 動(dòng)態(tài)方法解析(dynamic method resolution)
    先征詢接收者所屬的類习绢,看其是否能動(dòng)態(tài)添加方法渠抹,以處理當(dāng)前這個(gè)“未知的選擇子”。
  2. 涉及完整的消息轉(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種方式:

  1. 指派“委托方法”墩崩。
  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),但不建議這樣做赚哗,原因:

  1. 會(huì)有很多重復(fù)代碼她紫。
  2. 在內(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ò)展。用法:

  1. 可用它向類中新增實(shí)例變量够掠。
  2. 如果某個(gè)屬性在主接口中聲明為 readonly民褂,那么可在類的內(nèi)存聲明擴(kuò)展,然后再將屬性聲明為 readwrite疯潭。
  3. 用來(lái)聲明私有方法的原型赊堪,雖然新版編譯器不強(qiáng)制使用方法前必須先聲明。
  4. 如果希望所遵循的協(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ì)列,少用同步鎖

  1. 同步鎖在旱,在極端情況下會(huì)導(dǎo)致死鎖
@synchronized(id) {
     // TODO
}

頻繁使用同步鎖摇零,會(huì)降低代碼效率,因?yàn)楣灿猛粋€(gè)鎖的那些同步塊桶蝎,都必須按照順序執(zhí)行驻仅。

  1. 使用 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種方式:

  1. for 循環(huán)
  2. NSEnumerator 遍歷
  3. 快速遍歷:for in
  4. "塊枚舉法"

塊枚舉法本身能通過(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

原因:

  1. 當(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ì)象朝墩。

  2. NSCache 不會(huì)『拷貝』鍵醉拓,而是保留它。
    原因:很多時(shí)候收苏,鍵都是由不支持拷貝操作的對(duì)象來(lái)充當(dāng)亿卤。

  3. NSCache 是線程安全的,而 NSDictionary 則絕對(duì)不具備這優(yōu)勢(shì)鹿霸。

可以給 NSCache 對(duì)象設(shè)置上限排吴,用以控制緩存

有2個(gè)與系統(tǒng)資源相關(guān)的尺度可供調(diào)整:

  1. 是緩存中的對(duì)象總數(shù)。
  2. 所有對(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ī)則:

  1. 如果某個(gè)類不實(shí)現(xiàn) load 方法,無(wú)論超類是否有實(shí)現(xiàn) load 方法贡翘,都不會(huì)調(diào)用蹈矮。
  2. 分類和類都有可能出現(xiàn) load 方法,若是有這種情況鸣驱,系統(tǒng)先調(diào)用類中的泛鸟,再調(diào)用分類的。

但現(xiàn)在已經(jīng)很少使用 load 了丐巫,若是有使用谈况,load 中的代碼需要盡量精簡(jiǎn)。

initialize VS load

  1. initialize 是惰性調(diào)用的递胧,只有程序用到相關(guān)類時(shí)碑韵,才會(huì)調(diào)用,但對(duì) load 來(lái)說(shuō)缎脾,應(yīng)用程序必須阻塞并等著所有類的 load 都執(zhí)行完祝闻,才能繼續(xù)。
  2. 運(yùn)行期系統(tǒng)在執(zhí)行 initialize 時(shí)遗菠,是處于正常狀態(tài)的联喘,所以可以安全使用并調(diào)用任意類中的任意方法,而且運(yùn)行期系統(tǒng)也會(huì)確保 initialize 是線程安全的辙纬,即只有執(zhí)行 initialize 的線程可以操作類或其實(shí)例豁遭,其他線程會(huì)先阻塞,等待 initialize 執(zhí)行完贺拣。
  3. 關(guān)于繼承規(guī)則蓖谢,initialize 跟其他消息一樣捂蕴,即使沒(méi)有實(shí)現(xiàn)它,而其超類實(shí)現(xiàn)了闪幽,就會(huì)調(diào)用超類的實(shí)現(xiàn)代碼啥辨。

精簡(jiǎn) initialize 的原因:

  1. 大家不希望程序掛起。
    對(duì)于某個(gè)類來(lái)說(shuō)盯腌,任何線程都可能成為初次用到它的那個(gè)線程溉知,并導(dǎo)致其阻塞,如果那個(gè)線程碰巧是 UI 線程腕够,就會(huì)導(dǎo)致程序無(wú)響應(yīng)级乍。
  2. 開(kāi)發(fā)者無(wú)法控制類的初始化時(shí)機(jī)。
    運(yùn)行期系統(tǒng)更新后帚湘,也有可能會(huì)修改類的初始化方式卡者。
  3. 如果代碼很復(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)引用注簿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末契吉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子诡渴,更是在濱河造成了極大的恐慌捐晶,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妄辩,死亡現(xiàn)場(chǎng)離奇詭異惑灵,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)眼耀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)英支,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人哮伟,你說(shuō)我怎么就攤上這事干花⊙煊欤” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵把敢,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我谅辣,道長(zhǎng)修赞,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任桑阶,我火速辦了婚禮柏副,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蚣录。我一直安慰自己割择,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布萎河。 她就那樣靜靜地躺著荔泳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪虐杯。 梳的紋絲不亂的頭發(fā)上玛歌,一...
    開(kāi)封第一講書(shū)人閱讀 51,521評(píng)論 1 304
  • 那天,我揣著相機(jī)與錄音擎椰,去河邊找鬼支子。 笑死,一個(gè)胖子當(dāng)著我的面吹牛达舒,可吹牛的內(nèi)容都是我干的值朋。 我是一名探鬼主播,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼巩搏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼昨登!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起贯底,我...
    開(kāi)封第一講書(shū)人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤篙骡,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后丈甸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體糯俗,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年睦擂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了得湘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡顿仇,死狀恐怖淘正,靈堂內(nèi)的尸體忽然破棺而出摆马,到底是詐尸還是另有隱情,我是刑警寧澤鸿吆,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布囤采,位于F島的核電站,受9級(jí)特大地震影響惩淳,放射性物質(zhì)發(fā)生泄漏蕉毯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一思犁、第九天 我趴在偏房一處隱蔽的房頂上張望代虾。 院中可真熱鬧,春花似錦激蹲、人聲如沸棉磨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)乘瓤。三九已至,卻和暖如春策泣,著一層夾襖步出監(jiān)牢的瞬間馅扣,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工着降, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留差油,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓任洞,卻偏偏與公主長(zhǎng)得像蓄喇,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子交掏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

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

  • 這里僅記錄對(duì)我有幫助的內(nèi)容妆偏,不是對(duì)這本書(shū)的全面概括。有些我目前還沒(méi)怎么用過(guò)的東西盅弛,比如GCD钱骂,現(xiàn)在讀來(lái)還沒(méi)什么感覺(jué)...
    倫啊倫閱讀 315評(píng)論 0 1
  • 第一章 1. Objective-C 使用的是消息結(jié)構(gòu)而非函數(shù)調(diào)用,其區(qū)別在于: 消息結(jié)構(gòu)的語(yǔ)言挪鹏,其運(yùn)行時(shí)所應(yīng)執(zhí)行...
    鄭嘉成_閱讀 660評(píng)論 3 11
  • Effective Objective-C讀書(shū)筆記见秽,記錄書(shū)中的總結(jié)點(diǎn),加入了一些例子讨盒,方便理解和后期回顧解取。 一、熟...
    peaktan閱讀 379評(píng)論 0 2
  • 一 熟悉Objective-C 了解Objective-C語(yǔ)言的起源 在類的頭文件中盡量少引入其他頭文件 除非確有...
    gamper閱讀 240評(píng)論 0 3
  • 驛站今天真熱鬧返顺, 各路高手逞英豪禀苦。 詩(shī)書(shū)畫(huà)影展風(fēng)采蔓肯, 板橋再世也折腰。
    小車16閱讀 158評(píng)論 0 0