此文是《Effective Objective-C 2.0 編寫高質量iOS與OS X代碼的52個有效方法》的閱讀筆記
目錄
第1章 熟悉Objective-C
- 第1條:了解Objective-C語言的起源
- 第2條:在類的頭文件中盡量少引入其他頭文件
- 第3條:多用字面量語法喉童,少用與之等價的方法
- 第4條:多用類型常量,少用#define預處理指令
- 第5條:用枚舉表示狀態(tài)暇藏、選項家乘、狀態(tài)碼
第2章 對象蜜暑、消息卷玉、運行期
- 第6條:理解“屬性”這一概念
- 第7條:在對象內部盡量直接訪問實例變量
- 第8條:理解“對象等同性”這一概念
- 第9條:以“類族模式”隱藏實現(xiàn)細節(jié)
- 第10條:在既有類中使用關聯(lián)對象存在自定義數(shù)據(jù)
- 第11條:理解objc_msgSend的作用
- 第12條:理解消息轉發(fā)機制(
推薦看原文
) - 第13條:用“方法調配技術”調試“黑盒方法”
- 第14條: 理解“類對象”的用意
第3章 接口與API設計
- 第15條:用前綴避免命名空間沖突
- 第16條:提供“全能初始化方法”
- 第17條:實現(xiàn)description方法
- 第18條:盡量使用不可變對象
- 第19條:使用清晰而協(xié)調的命名方法
- 第20條:為私有方法名加前綴
- 第21條:理解Objective-C錯誤模型
- 第22條:理解NSCopying協(xié)議
第4章 協(xié)議與分類
- 第23條:通過委托與數(shù)據(jù)源協(xié)議進行對象間通信
- 第24條:將類的實現(xiàn)代碼分散到便于管理的數(shù)個分類之中
- 第25條:總是為第三方類的分類名稱加前綴
- 第26條:勿在分類中聲明屬性
- 第27條:使用"class-continuation分類"隱藏實現(xiàn)細節(jié)
- 第28條:通過協(xié)議提供匿名對象
第5章 內存管理
- 第29條:理解引用計數(shù)
- 第30條:以ARC簡化引用計數(shù)
- 第31條:在dealloc方法中只釋放引用并解除監(jiān)聽
- 第32條:編寫“異常安全代碼”時留意內存管理問題
- 第33條:以弱引用避免保留環(huán)
- 第34條:以“自動釋放池塊”降低內存峰值
- 第35條:用“僵尸對象”調試內存管理問題(
推薦看原文
) - 第36條:不要使用retainCount
第6章 塊與大中樞派發(fā)
- 第37條:理解“塊”這一概念
- 第38條:為常用的塊類型創(chuàng)建typedef
- 第39條:用handler塊降低代碼分散程度
- 第40條:用塊引用其所屬對象時不要出現(xiàn)保留環(huán)
- 第41條:多用派發(fā)隊列丈牢,少用同步鎖
- 第42條:多用GCD鳞青,少用performSelector系列方法
- 第43條:掌握GCD及操作隊列的使用時機
- 第44條:通過Dispatch Group機制,根據(jù)系統(tǒng)資源狀況來執(zhí)行任務
- 第45條:使用dispatch_once來執(zhí)行只需運行一次的線程安全代碼
- 第46條:不要使用dispatch_get_current_queue
第7章 系統(tǒng)框架
- 第47條:熟悉系統(tǒng)框架
- 第48條:多用塊枚舉铃肯,少用for循環(huán)
- 第49條:對自定義其內存管理語義的collection使用無縫橋接
- 第50條:構建緩存時選用NSCache而非NSDictionary
- 第51條:精簡initialize與load的實現(xiàn)代碼
- 第52條:別忘了NSTimer會保留其目標對象
===================================
第一章 熟悉Objective-C
===================================
第1條:了解Objective-C語言的起源
- Objective-C由Smalltalk(消息型語言的鼻祖)演化而來患亿,使用的是“消息結構”(messageing structure)而非"函數(shù)調用"(functioncalling),這兩者之間的區(qū)別就像這樣:
// Messaging (Objective-C)
Object *obj = [Object new];
[obj performWith: parameter1 and: parameter2];
// Function calling (C++)
Object *obj = new Object;
obj->perform(parameter1, parameter2);
- Objective-C 為C語言添加了對象特性缘薛,是其超集窍育。Objective-C使用動態(tài)綁定的消息結構,也就是說宴胧,在運行時才會檢查對象類型漱抓。接收一條消息之后,究竟應執(zhí)行何種代碼恕齐,由運行期環(huán)境而非編譯器來決定乞娄。
- Objective-C語言中的指針是用來指示對象的,聲明一個變量显歧,令其指代某個對象的語法基本上是照搬C語言的仪或。
NSString *someString = @"The string";
變量someString指向分配在堆里的某塊內存,其中含有一個NSString的對象"The string"。另外Objective-C的對象分配總是分配在"堆空間"(heap space)中,有些不含有*的變量嘁扼,它們可能會使用棧空間(stack space)到旦,例如CGRect類型的分配。而這些分配在"堆空間"的對象巨缘,需要內存管理(現(xiàn)在不用手動管理了)添忘。
第2條:在類的頭文件中盡量少引入其他的頭文件
- 當不需要知道某個類的全部細節(jié)時,用
@class TheClass
若锁,來替代#import "TheClass.h"
, 這叫做“向前聲明”(forward declaring)該類搁骑。@class
的使用既可以減少不必要的導入,也可以避免頭文件因互相import而編譯通不過又固。
// EOCPerson.m
# import "EOCPerson.h"
// # import "EOCEmployer.h"
@class EOCEmployer;
@interface EOCPerson : NSObject
@property (nonatomic, strong) EOCEmployer *employer;
@end
將引入的頭文件時機盡量延后仲器,只在確有需要時才引入,這樣就可以減少類的使用者所需引入的頭文件數(shù)量, 減少編譯時間仰冠。
# import "ViewController.h"
# import "EOCPerson.h"
# import "EOCEmployer.h"
@interface ViewController()
@end
@implementation
- (void)viewDidload {
[super viewDidload];
EOCPerson *person = [EOCPerson new];
EOCEmployer *employer = [EOCEmployer new];
employer.name = "Coder"; // 如果沒有 # import "EOCEmployer.h"會編譯報錯
person.employer = employer;
}
@end
- 聲明類遵循某個協(xié)議時娄周,最好把協(xié)議單獨放在一個頭文件中,再來導入沪停。然而有些協(xié)議,例如“委托協(xié)議”(delegate protocol),就不用單獨寫一個頭文件了木张。在那種情況下众辨,協(xié)議只有與接收協(xié)議委托的類放在一起定義才有意義。
第3條:多用字面量語法舷礼,少用與之等價的方法
- 字面量語法(literal syntax)實際上只是一種“語法糖”(syntactic sugar),例如普通的創(chuàng)建NSNumber對象是:
NSNumber *someNumber = [NSNumber numberWithInt:1];
, 字面量的語法是NSNumber *someNumber = @1;
- 應該使用字面量語法來創(chuàng)建字符串鹃彻、數(shù)值、數(shù)組妻献、字典蛛株。與創(chuàng)建此類對象的常規(guī)方法相比,這么做更加簡明扼要育拨。
- 應該通過取下標操作來訪問數(shù)組下標或字典中的鍵所對應的元素谨履。
- 用字面量語法創(chuàng)建數(shù)組或字典時,若值中有nil熬丧,則會拋出異常笋粟。因此務必確保值里不含nil。
- 字面量語法的局限性:除了字符串之外析蝴,所創(chuàng)建出來的對象必須屬于Foundation框架才行害捕。如果定義了這些類的子類,則無法用字面量創(chuàng)建對象闷畸。而且使用字面量語法創(chuàng)建的字符串尝盼、數(shù)組、字典對象都是不可變的(immutable)佑菩。
第4條:多用類型常量盾沫,少用#define預處理指令
- 不要用預處理指令定義常量。這樣定義出來的常量不含類型信息倘待,編譯器只是會在編譯前據(jù)此執(zhí)行查找與替換操作疮跑。即使有人重新定義了常量值,編譯器也不會產生警告信息凸舵,這將導致應用程序中的常量不一致祖娘。
- 在實現(xiàn)文件中使用static const來定義“只在編譯單元內可見的常量”(translation-unit-specitfic constant)。另外啊奄,在Objective-C的語境下渐苏,“編譯單元”一詞通常指每個類的實現(xiàn)文件(以.m為后綴名)。通常命名時以"k"作為前綴菇夸,然后駝峰命名琼富。
static const NSTimeInterval kAnimationDuration = 0.3
- 在頭文件中使用extern來聲明全局變量,并在相關實現(xiàn)文件中定義其值庄新。這種常量要出現(xiàn)在全局符號表中鞠眉,所以其名稱應加以區(qū)隔,通常用與之相關的類名加前綴薯鼠。Objective-C沒有“名稱空間”(namespace)這一概念,以類名作為前綴械蹋,是為了避免命名沖突出皇。例如UIKit就按照這種方式來聲明用作通知名稱的全局常量哗戈。其中有類似UIApplicationDidEnterBackgroundNotification與UIApplicationWillEnerForegroundNotification這樣的常量名郊艘。
// EOCLoginManager.h
#import <Foundation/Foundation.h>
// 此常量需放在“全局符號表”(global symbol table)中,以便可以在定義該常量的編譯單元之外使用唯咬。
extern NSString *const EOCLoginManagerDidLoginNotification;
@interface EOCLoginManager : NSObject
- (void)login;
@end
// EOCLoginManager.m
#import "EOCLoginManager.h"
NSString *const EOCLoginManagerDidLoginNotification = @"EOCLoginManagerDidLoginNotification";
@implementation EOCLoginManager
@end
第5條:用枚舉表示狀態(tài)纱注、選項、狀態(tài)碼
- 應該用枚舉來表示狀態(tài)機的狀態(tài)胆胰、傳遞給方法的選項以及狀態(tài)碼等值狞贱,給這些值起個易懂的名字。
- 如果把傳遞給某個方法的選項表示為枚舉類型煮剧,而多個選項又可同時使用斥滤,那么就將個選項值定義為2的冪,以便通過按位或操作將其組合起來勉盅。
- 用NS_ENUM與NS_OPTIONS宏來定義枚舉類型佑颇,并指明其底層數(shù)據(jù)類型。這樣做可以確保枚舉是用開發(fā)者所選的底層數(shù)據(jù)類型實現(xiàn)出來的草娜,而不會采用編譯器所選的類型挑胸。
- 在處理枚舉類型的switch語句中不要實現(xiàn)default分支。這樣的話宰闰,加入新枚舉之后茬贵,編譯器就會提示開發(fā)者:switch 語句并未處理所有枚舉。
===================================
第二章 對象移袍、消息解藻、運行期
===================================
第6條:理解“屬性”這一概念
- “屬性”(property)是Objective-C的一項特性,用于封裝對象中的數(shù)據(jù)葡盗。利用@property聲明屬性螟左,可以快速方便得為實例變量創(chuàng)建存取器set和get方法。@property還允許我們用點語法使用存取器觅够。
@interface Person : NSObject
@property NSString *name;
@end
- @synthesize 的作用:是為屬性添加一個實例變量名胶背,或者說別名。同時會為該屬性生成 setter/getter 方法喘先。
/*
_name是實例變量钳吟,name是屬性。告訴編譯器name屬性為_name實例變量生成setter and getter方法的實現(xiàn)
*/
@synthesize name = _name
- 屬性特質
@property (nonatomic, readonly, assign, getter=isOpen) BOOL open;
- 【原子性】默認情況下窘拯,由編譯器所合成的方法會通過鎖定機制確保其原子性(atomicity)红且。如果屬性具備nonatomic特質坝茎,則不使用同步鎖。
- 【讀直焙、寫權限】a.具備readwirte特質的屬性擁有“獲取方法(getter)“與“設置方法(setter)”景东。若該屬性由@synthesize實現(xiàn),則編譯器會自動生成這兩個方法奔誓。b.具備readonly特質的屬性僅擁有獲取方法,只有當該屬性有@synthesize實現(xiàn)時搔涝,編譯器才會為其合成獲取方法厨喂。你可以用此特質把某個屬性對外公開為只讀屬性,然后在"class-continuation分類"中將其重新定義為讀寫屬性庄呈。
- 【內存管理語義】
3.1assign
"設置方法"只會執(zhí)行針對“純量類型”(scalar type,例如CGFloat或NSInteger等)的簡單賦值操作蜕煌。
3.2strong
此特質表明該屬性定義了一種“擁有關系”(owning relationship)。為這種屬性設置新值時诬留,設置方法會先保留新值斜纪,并釋放舊值,然后再將新值設置上去
3.3weak
表明了一種“非擁有關系”(nonowning relationship)文兑。為這種屬性設置新值時盒刚,設置方法既不保留新值,也不釋放舊值绿贞。此特質同assign類似因块,然后在屬性所指的對象遭到摧毀時,屬性值也會清空籍铁。
3.4unsafe_unretained
語義和assign相同涡上,但是它適用于“對象類型”,該特質表達一種“非擁有關系”(“不保留”拒名,unretained)吩愧,當目標對象遭到摧毀時,屬性值不會自動清空(“不安全”增显,unsafe)雁佳,這一點與weak有區(qū)別。
3.5copy
此特質所表達的屬性關系與strong類似甸怕。然而設置方法并不保留新值甘穿,而是將其"拷貝"(copy)。 - 【方法名】
- getter=<name> 指定“獲取方法”的方法名梢杭。
- setter=<name> 指定“設置方法”的方法名温兼。
- 在設置屬性對應的實例變量時,一定要遵從該屬性所聲明的語義武契。
- (id)initWithzName: (NSString *)name {
if (self = [super init]) {
_name = [name copy];
}
return self;
}
- 開發(fā)iOS程序時應該使用nonatomic募判,因為atomic屬性會嚴重影響性能(而且atomic并不能保證線程安全荡含,若要實現(xiàn)“線程安全”的操作,還需要更為深層的鎖定機制才行)届垫。
第7條:在對象內部盡量直接訪問實例變量
- 在對象內部讀取數(shù)據(jù)時释液,應該直接通過實例變量來讀(不經(jīng)過Objective-C“方法派發(fā)”,訪問實例變量速讀比較快)装处,而寫入數(shù)據(jù)時误债,則應通過屬性來寫。
- 在初始化方法及dealloc方法中妄迁,總是應該直接通過實例變量來讀寫數(shù)據(jù)寝蹈。
- 有時會使用惰性初始化(lazy initialization)技術配置某份數(shù)據(jù),這種情況下登淘,需要通過屬性來讀取數(shù)據(jù)箫老。
第8條:理解“對象等同性”這一概念
- 若想檢測對象的等同性,請?zhí)峁癷sEqual”與hash方法黔州。
- 相同的對象必須具有相同的哈希碼耍鬓,但是兩個哈希碼相同的對象卻未必相同。
- 不要盲目地逐個檢測每條屬性流妻,而是應該按照具體需求來指定檢測方案牲蜀。
- 編寫hash方法時,應該使用計算速度快而且哈希碼碰撞幾率低的算法合冀。
例如
- (NSUInteger) hash {
NSUInteger firstNameHash = [_firstName hash];
NSUInteger lastNameHash = [_lastName hash];
NSUInteger ageHash = _age;
return firstNameHash ^ lastNameHash ^ ageHash;
}
第9條:以“類族模式”隱藏實現(xiàn)細節(jié)
- “類族(class cluster)”是一種很有用的模式(pattern)各薇,可以隱藏“抽象基類”(abstract base class)背后的實現(xiàn)細節(jié)。Objective-C的系統(tǒng)框架中普遍使用此模式君躺。比如UIKit的UIButton類峭判。創(chuàng)建按鈕,需要調用類方法:
+ (UIButton *)buttonWithType: (UIButtonType)type;
棕叫。還有林螃,NSnumber類也是類族。 - 大部分collection類都是類族俺泣,例如NSArray與其可變版本NSMutableArray疗认。
- 子類應該繼承自類族中的抽象基類。(可惜Objective-C這門語言沒辦法指明某個基類是“抽象的”,通常是在文檔中寫明)伏钠。
- 子類應該定義自己的數(shù)據(jù)存儲方式横漏。
開發(fā)者編寫NSArray子類時,經(jīng)常在這個問題上受阻熟掂。子類必須用一個實例變量來存放數(shù)組中的對象缎浇。這似乎與大家預想的不同,我們以為NSArray自己肯定會保存那些對象赴肚,所以在子類中就無須再保存一份了素跺。但是大家要記住二蓝,NSArray本身只不過是包在其他隱藏對象外面的殼,它僅僅定義了所有數(shù)組都具備的一些接口指厌。對于這個自定義的數(shù)組子類來說刊愚,可以用NSArray來保存其實例。 - 子類應該覆寫超類文檔中指明需要覆寫的方法踩验。
- 從類族的公共抽象基類中繼承子類時要當心鸥诽,若有開發(fā)文檔,則應首先閱讀箕憾。
第10條:在既有類中使用關聯(lián)對象存放自定義數(shù)據(jù)
- “關聯(lián)對象”衙传, 可以給某個對象關聯(lián)許多其他對象,這些對象通過來“鍵”來區(qū)分厕九。存儲對象的時候,可以指明“存儲策略”(storage policy)地回,用以維護相應的“內存管理語義”扁远。存儲策略由名為objc_AssociationPolicy的枚舉所定義:
關聯(lián)類型 | 等效的@property屬性 |
---|---|
OBJC_ASSOCIATION_ASSIGN | assign |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic, retain |
OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic, copy |
OBJC_ASSOCIATION_RETAIN | retain |
OBJC_ASSOCIATION_COPY | copy |
- 通過下列方法可以管理關聯(lián)對象:
- void objc_setAssociateObject(id object, void *key, id value, objc_AssociationPolicy policy)
此方法以給定的鍵和策略為某對象設置關聯(lián)對象 - id objc_getAssociateObject(id object, void *key)
此方法根據(jù)給定的鍵從某對象中獲取相應的關聯(lián)對象值。 - void objc_removeAssociatedObjects(id object)
此方法移除指定對象的全部關聯(lián)對象刻像。
- void objc_setAssociateObject(id object, void *key, id value, objc_AssociationPolicy policy)
- 在設置關聯(lián)對象時畅买,若想令兩個鍵匹配到同一個值,則二者必須是完全相同的指針才行细睡。鑒于此谷羞,在設置關聯(lián)對象值時,通常使用靜態(tài)全局變量做鍵溜徙。
- 只有在其他做法不可行時才應選用關聯(lián)對象湃缎,因為這種做法通常會引入難于查找的bug。
第11條:理解objc_msgSend的作用
- 給某個對象“調用方法”(call a method)蠢壹,相當于給某個對象“發(fā)送消息”(invoke a message)嗓违。
- 給對象發(fā)送消息可以這樣寫:
id returnValue = [someObject messageName:paramter];
。someObject叫做“接收者”(receiver)图贸,messagename叫做“選擇子”(seletor)蹂季。選擇子與參數(shù)合起來稱為“消息”(message)。編譯器看到此消息后疏日,將其轉換為一條標準的C語言函數(shù)調用偿洁,所調用的函數(shù)乃是消息傳遞機制中的核心函數(shù),叫做objc_msgSend沟优,其“原型”:void objc_msgSend(id self, SEL cmd, ...)
涕滋。 - 消息傳遞機制:objc_msgSend函數(shù)會依據(jù)接收者與選擇子的類型來調用適當?shù)姆椒ā榱送瓿纱瞬僮骶簧瘢摲椒ㄐ枰诮邮照咚鶎俚念愔兴褜て洹胺椒斜怼?list of methods)何吝,如果能找到與選擇子名稱相符的方法溉委,就跳轉至其實現(xiàn)代碼。若是找不到爱榕,那就沿著繼承體系繼續(xù)向上查找瓣喊,等找到合適的方法之后再跳轉。如果最終還是找不到相符的方法黔酥,那就執(zhí)行“消息轉發(fā)”(message forwarding)操作藻三。
- 消息傳遞機制:
第12條: 理解消息轉發(fā)機制
消息轉發(fā)分為兩大階段。
- 第一階段:動態(tài)方法解析:先征詢接收者跪者,所屬的類棵帽,看是否能動態(tài)添加方法,以處理當前這個“未知的選擇子”(unknown selector)渣玲。這個過程是調用類方法:
+ (BOOL)resolveInstanceMethod: (SEL)selector
(如果需要新增方法逗概,就要在這個方法里面進行處理并返回YES) - 第二階段:第一階段執(zhí)行完后,接收者自己就無法再以動態(tài)新增方法的手段來響應包含該選擇子的消息了忘衍。這時逾苫,運行期系統(tǒng)會請求接收者以其他手段來處理消息相關的方法調用。分為兩小步:
- 尋找備援接收者枚钓。調用
- (id)forwardingTargetForSelector:(SEL)selector
铅搓,運行期系統(tǒng)會問它:能不能把這條消息轉給其他接收者來處理。在一個對象內部搀捷,可能還有一系列其他對象星掰,該對象可經(jīng)由此方法將能夠處理某選擇子的相關內部對象返回,這樣的話嫩舟,在外界看來氢烘,好像是該對象親自處理了這些消息似的。 - 完整的消息轉發(fā)至壤。到這一步威始,系統(tǒng)會調用
- (void)forwardInvocation: (NSInvocation *)invocation
,其中invocation對象攜帶了那條尚未處理的消息有關的全部細節(jié)。在這里將消息指派給目標對象像街,然而這樣實現(xiàn)出來的方法與“備援接收者”方案所實現(xiàn)的方法等效黎棠,所以比較少用。
- 尋找備援接收者枚钓。調用
第13條:用“方法調配技術”調試“黑盒方法”
- 類的方法列表會把選擇子的名稱映射到相關的方法實現(xiàn)之上镰绎,使得“動態(tài)消息派發(fā)系統(tǒng)”能夠據(jù)此找到應該調用的方法脓斩。這些方法均以函數(shù)指針的形式表示,這種指針叫做IMP畴栖。
- 方法調配(method swizzing):在運行期随静,向類中新增或替換選擇子對應的方法實現(xiàn),常用來向原有實現(xiàn)中添加新功能。
第14條:理解“類對象”的用意
- Objective-C對象本質是一個結構體燎猛,該對象結構體的首個成員是Class類的變量恋捆。
typedef struct objc_object {
Class isa;
} *id;
- Class對象也是一個結構體,此結構體存放類的“元數(shù)據(jù)”(metadata)重绷,首個變量也是isa指針沸停,這說明Class本身亦為Objective-C對象。結構體里還有個變量叫做super_class昭卓,它定義了本類的超類愤钾。類對象所屬的類型(也就是isa指針所指向的類型)是另外一個類,叫做“元類”(metaclass)候醒,用來表述類對象本身所具備的元數(shù)據(jù)能颁。“類方法”就定義與此處倒淫,因為這些方法可以理解成類對象的實例方法伙菊。每個Class僅有一個“類對象”,而每個“類對象”僅有一個與之相關的“元類”敌土。
typedef struct objc_class *Class;
struct objc_class {
Class isa;
Class super_class;
const char *name;
long version;
long info;
struct objc_ivar_list *ivars;
struct objc_method_list **methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocols;
}
super_class指針確立了繼承關系占业,而isa指針描述了實例所屬的類。通過這張布局關系圖即可執(zhí)行“類型信息查詢”纯赎。
- 可以用類型信息查詢方法來檢視類繼承體系。"isMemberOfClass":能夠判斷出對象是否為某個特定類的實例南蹂,而"isKindOfClass":則能夠判斷出對象是否為某類或其派生類的實例犬金。
- 如果對象類型無法在編譯器確定,那么就應該使用類型信息查詢方法來探知六剥。
- 盡量使用類型信息查詢方法來確定對象類型晚顷,而不要直接比較類對象,因為某些對象可能實現(xiàn)了轉發(fā)功能疗疟。
===================================
第三章 接口與API設計
===================================
第15條:用前綴避免命名空間沖突
- Objective-C沒有其他語言那種內置的命名空間(namespace)機制该默。所以為了避免命名沖突的唯一辦法就是變相實現(xiàn)命名空間:為所有名稱都加上適當前綴。Apple宣稱其保留使用所有"兩字母前綴"(two-letter prefix)的權利; 可以選擇與你的公司策彤、應用程序或二者皆有關聯(lián)之名稱作為類名的前綴栓袖。
第16條:提供“全能初始化方法”
- “全能初始化方法”(designated initializer):可為對象提供必要信息以便其能完成工作的初始化方法。例如:
- (id)init
- (id)initWithString:(NSString *)string
- (id)initWithTimeIntervalSingNow:(NSTimeInterval)seconds
- (id)initWithTimeInterval:(NSTimeInterval)seconds
sinceDate:(NSDate *)refDate
- (id)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)seconds
- (id)initWithTimeIntervalSince1970:(NSTimeInterval)seconds
其中的- (id)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)seconds
是全能初始化方法店诗。也就是裹刮,其余的初始化方法都要調用它。于是庞瘸,只有在全能初始化方法中捧弃,才會存儲內部數(shù)據(jù)。
- 在類中提供一個全能初始化方法,并于文檔里指明违霞。其他初始化方法均應調用此方法嘴办。
- 若全能初始化方法與超類不同,則需覆寫超類中的對應方法买鸽。
- 如果超類的初始化方法不適用于子類涧郊,那么應該覆寫這個超類方法,并在其中拋出異常癞谒。
第17條:實現(xiàn)description方法
- 實現(xiàn)description方法返回一個有意義的字符串底燎,用以描述該實例
- 若想在調試時打印更詳盡的對象描述信息,則應實現(xiàn)弹砚。debugDescripiton方法(在NSObject類的默認實現(xiàn)中双仍,此方法只是直接調用了description)。
第18條:盡量使用不可變對象
- 盡量創(chuàng)建不可變的對象桌吃。
- 若某屬性僅可于對象內部修改朱沃,則在“class-continuation分類”中將readonly屬性擴展為readwrite屬性。
- 不要把可變的collection作為屬性公開茅诱,而應提供相關方法逗物,以此修改對象中的可變collection。
第19條:使用清晰而協(xié)調的命名方式
- 方法名要言簡意賅瑟俭,從左至右讀起來更像個日常用語中的句子才行好翎卓。
- 方法命名:
- 如果方法的返回值是新創(chuàng)建的,那么方法的首個詞應是返回值的類型摆寄,除非前面還有修飾語失暴,例如localizedString。屬性的存取方法不遵守這種命名方式微饥,因為一般認為這些方法不會創(chuàng)建新對象逗扒,即便有時返回內部對象的一份拷貝,我們也認為那相當于原有的對象欠橘。這些存取方法應該按照其所對應的屬性來命名矩肩。
- 應該把表示參數(shù)類型的名詞放在參數(shù)前面。
- 如果方法要在當前對象上執(zhí)行操作肃续,那么應該包含動詞黍檩;若執(zhí)行操作時還需要參數(shù),則應該在動詞后面加上一個或多個名詞始锚。
- 不要使用str這種簡稱建炫,應該用string這樣的全稱。
- Boolean屬性應加is前綴疼蛾。如果某方法返回非屬性的Boolean值肛跌,那么應該根據(jù)其功能吃粒,選用has或is當前綴咏窿。
- 將get這個前綴留給那些由“輸出參數(shù)”(out-parameter)來保存返回值的方法,比如說,把返回值填充到“C語言式數(shù)組”(C-style array)里的那種方法就可以使用這個詞做前綴谊囚。
- (void)getCharacter:(unichar *)buffer range:(NSRange)aRange
- 類與協(xié)議的命名:如果要從其他框架中繼承子類晚岭,那么務必遵守其命名慣例嗤无。比方說壶愤,要從UIView類中繼承自定義的子類,那么類名 末尾的詞必須是view乔夯。同理砖织,若要創(chuàng)建自定義的委托協(xié)議,則其名稱中應該包含委托發(fā)起方的名稱末荐,后面再跟上Delegate一詞侧纯。
第20條:為私有方法名加前綴
- 把私有方法標出來,這樣很容易就能看出哪些方法可以隨意修改甲脏,哪些不應輕易改動眶熬。
- 給私有方法的名稱加上前綴,這樣可以很容易地將其同公共方法區(qū)分開块请。但是不要單用一個下劃線做私有方法的前綴娜氏,因為這種做法是預留給蘋果公司用的(Apple文檔),可以考慮用“p_”做前綴,或者用基于公司或者項目的“xx_”形式墩新。
第21條:理解Objective-C錯誤模型
- Objective-C在默認情況下不是“異常安全的”(exception safe)贸弥。具體來說,這意味著:如果拋出異常海渊,那么本應在作用域末尾釋放的對象現(xiàn)在卻不會自動釋放了茂腥。
- 如果想生成“異常安全”的代碼,可以通過設置編譯器的標志來實現(xiàn)切省,不過這將引入一些額外代碼,在不拋出異常時帕胆,也照樣執(zhí)行這部分代碼朝捆。需要打開的編譯器標志叫做 -fobjc-arc-exceptions。換句話說在ARC中異忱帘可能會導致對象的內存泄露芙盘。如果開啟了該選項,則ARC會額外為異常中的對象申請和釋放操作添加代碼脸秽,保證異常中ARC管理的對象也不會造成內存泄露儒老。當然這樣一來缺點就是可能會生成大量平常可能根本用不到的代碼记餐。(只有發(fā)生異常才會執(zhí)行)
- 在出現(xiàn)非致命錯誤(nonfatal error)時驮樊,Objective-C語言所用的編程范式為:令方法返回nil/0,或是使用NSError,以表明其中 有錯誤發(fā)生。
- NSError對象里封裝了三條信息:
1. Error domain(錯誤范圍,其類型為字符串)囚衔。也就是產生錯誤的根源挖腰,通常用一個特有的全局變量來定義。
2. Error code(錯誤碼练湿,其類型為整數(shù))獨有的錯誤代碼猴仑,用以指明在某個范圍內具體發(fā)生了何種錯誤。
3. User info(用戶信息肥哎,其類型為字典)有關此錯誤的額外信息辽俗,其中或許包含一段“本地化的描述”(localized description)。 - 在處理錯誤時篡诽,除了可以指派“委托方法”(delegate method)來處理錯誤崖飘,也可以把錯誤信息放在NSError對象里,經(jīng)由“輸出參數(shù)”返回給調用者霞捡。
NSError *error = nil;
Bool ret = [object doSomething: &error];
if (error) {
// There was an error
}
第22條:理解NSCopying協(xié)議
- 若想令自己所寫的對象具有拷貝功能坐漏,則需實現(xiàn)NSCopying協(xié)議。
- 如果自定義的對象分為可變版本與不可變版本碧信,那么就要同時實現(xiàn)NSCopying與NSMutableCopying協(xié)議赊琳。
- 復制對象時需決定采用淺拷貝還是深拷貝,一般情況下應該盡量執(zhí)行淺拷貝砰碴。
- 如果你所寫的對象需要深拷貝躏筏,那么可考慮新增一個專門執(zhí)行深拷貝的方法。
===================================
第四章 協(xié)議與分類
===================================
第23條:通過委托與數(shù)據(jù)源協(xié)議進行對象間通信
- 委托模式為對象提供了一套接口呈枉,使其可由此將相關事件告知其他對象趁尼。
- 將委托對象應該支持的接口定義成協(xié)議,在協(xié)議中把可能需要處理的事件定義成方法猖辫。
- 當某對象需要從另外一個對象中獲取數(shù)據(jù)時酥泞,可以使用委托模式。這種情境下啃憎,該模式亦成“數(shù)據(jù)源協(xié)議”(data source protocal)芝囤。
- 若有必要,可實現(xiàn)含有位段的結構體(位段結構既能夠節(jié)省空間辛萍,又方便于操作)悯姊,將委托對象是否能相應相關協(xié)議方法這一信息緩存至其中。
struct {
unsigned int didReceiveData: 1;
unsigned int didFailWithError: 1;
unsigned int didUpdateProgressTo: 1;
} _delegateFlags;
第24條:將類的實現(xiàn)代碼分散到便于管理的數(shù)個分類之中
- 使用分類把類的實現(xiàn)代碼劃分成易于管理的小塊
- 將應該視為“私有”的方法歸入名叫Private的分類中贩毕,以隱藏實現(xiàn)細節(jié)
第25條:總是為第三方類的分類名稱加前綴
- 分類機制通常用于向無源碼的既有分類中新增功能悯许。
- 向第三方類中添加分類時,總應給其名稱加上你專用的前綴辉阶。
例如NSString分類,NSString(ABC_HTTP)
先壕。 - 向第三方類中添加分類時瘩扼,總應給其中的方法名加上你專用的前綴。
例如:- (NSString *)abc_urlDecodedString;
第26條:勿在分類中聲明屬性
- 屬性是用來封裝數(shù)據(jù)的启上,所要表達的意思是邢隧,類中有數(shù)據(jù)在支撐著它。
- 在"class-continuation分類"之外的其他分類中冈在,可以定義存取方法(或者readonly屬性)倒慧,但盡量不要定義屬性“可以把封裝數(shù)據(jù)所用的全部屬性定義在主接口中纫谅。
第27條:使用"class-continuation分類"隱藏實現(xiàn)細節(jié)
- 通過"class-continuation分類"向類中新增實例變量。
- 如果屬性在主接口中聲明為“只讀”溅固,而類的內部又要用設置方法修改此屬性付秕,那么就在"class-continuation分類"中將其擴展為“可讀寫”。
- 把私有方法的原型聲明在"class-continuation分類"里面侍郭。
- 若想使類所遵守的協(xié)議不為人所知询吴,則可于"class-continuation分類"中聲明。
第28條:通過協(xié)議提供匿名對象
- 協(xié)議可在某種程度上提供匿名類型亮元。具體的對象類型可以淡化遵從某協(xié)議的id類型猛计,協(xié)議里規(guī)定了對象所應實現(xiàn)的方法。
- (id<EOCDatabaseConnection>)connectionWithIdentifiler:(NSString *)indentifier;
- 使用匿名對象來隱藏類型名稱(或類名)爆捞。
- 如果類型不重要奉瘤,重要的是對象能夠響應(定義在協(xié)議里的)特定方法,那么可使用匿名對象來表示煮甥。
===================================
第五章 內存管理
===================================
第29條:理解引用計數(shù)
- Objective-C語言使用引用計數(shù)來管理內存盗温,也就是說,每個對象都有個可以遞增或遞減的計數(shù)器成肘。如果想使某個對象繼續(xù)存活卖局,那就遞增其引用計數(shù);用完了之后双霍,就遞減其計數(shù)砚偶。計數(shù)變?yōu)?名,就表示沒人關注此對象了店煞,于是,就可以把它銷毀风钻。
- 在ARC工程中使用MRC: 在Project的Build Phase里面的Compile Source找到需要特殊處理的文件顷蟀,加上編譯選項(Compiler Flags),在文件后用
-fno-objc-arc
修飾就行了骡技。 - “懸掛指針”:為了避免在不經(jīng)意間使用了無效對象鸣个,一般調用完release之后都清空指針羞反。
NSNumber *number = [[NSNumber alloc] initWithInt:1337];
[array addObject: number];
[number release];
number = nil;
- 屬性存取方法中的內存管理。若屬性為"strong關系"(strong relationship)囤萤,則設置的屬性會保留昼窗。
- (void)setFoo:(id)foo {
[foo retain];
[_foo release];
_foo = foo;
}
此方法將保留新值并釋放舊值,然后更新實例變量涛舍,令其指向新值澄惊。順序很重要!
- 自動釋放池富雅。在Objective-C的引用計數(shù)架構中掸驱,自動釋放池是一項重要特性。調用release會立刻遞減對象的保留計數(shù)(而且還可能令系統(tǒng)回收對象)没佑,然而有時候可以不調用它毕贼,改為調用autorelease,此方法會在稍后遞減計數(shù)蛤奢,通常是在下一次“事件循環(huán)”(event loop時遞減)鬼癣,除非你有自己的自動釋放池。
- (NSString *)stringValue {
NSString *str = [[NSString alloc] initWithFormat:@"I am this: %@", self];
return [str autorelease];
}
- 通常采用“弱引用”(weak refefence)來打破保留環(huán)(retain cycle)啤贩。
第30條:以ARC簡化引用計數(shù)
- ARC環(huán)境下待秃,引用計數(shù)實際上還是要執(zhí)行的,只不過保留與釋放操作現(xiàn)在是由ARC自動為你添加瓜晤。ARC會自動執(zhí)行retain锥余、release、autorelease等操作痢掠。因為ARC自動調用這些方法時驱犹,并不通過普的通Objective-C消息派發(fā)機制,而是直接調用其底層C語言版本足画,所以不能覆寫這些方法雄驹,這些方法從來不會被直接調用。
- 在ARC管理對象生命期的辦法基本上就是:在合適的地方插入“保留”及“釋放”操作淹辞。在ARC環(huán)境下医舆,變量的內存管理語義可以通過修飾符(
_ _strong 、_ _weak象缀、_ _unsafe_unretained蔬将、_ _atutoreleasing
)指明,而原來則需要手工執(zhí)行“保留”及“釋放”操作央星。 - ARC只負責管理Objective-C對象的內存霞怀。尤其要注意:CoreFoundation對象不歸ARC管理,開發(fā)者必須適時調用CFRetain/CFRelease
第31條:在dealloc方法中只釋放引用并解除監(jiān)聽
- 在dealloc方法里莉给,應該做的事情就是釋放指向其他對象的引用毙石,并取消原來訂閱的“鍵值觀測”(KVO)或NSNotificationCenter等通知廉沮,不要做其他事情。
- 如果對象持有文件描述等系統(tǒng)資源徐矩,那么應該專門編寫一個方法來釋放此種資源滞时。這樣的類要和其使用者約定:用完資源后必須調用close方法。
- 執(zhí)行異步任務的方法不應在dealloc里調用滤灯;只能在正常狀態(tài)下執(zhí)行的那些方法也不應在dealloc里調用坪稽,因為此時對象已處于正在回收的狀態(tài)了。
第32條:編寫“異常安全代碼”時留意內存管理問題
- 應用程序因異常狀況而終止時才拋出異常力喷,因此刽漂,如果應用程序即將終止,那么是否還會發(fā)生內存泄漏就已經(jīng)無關緊要了。 但如果捕獲異常陈症,那么一定要注意將try塊內所創(chuàng)立的對象清理干凈优炬。
@try {
EOCSomeClass *object = [[EOCSomeClass alloc] init];
[object doSomethingThatMayThrow];
}
@catch (..) {
NSLog(@"Whoops, there was an error. Oh well..");
}
上面代碼情況是會發(fā)生內存泄漏的。
- 在默認情況下悄谐,ARC不生成安全處理異常所需的清理代碼。開啟編譯器標志(
-fobjc-arc-exceptions
)后,可生成這種代碼雾袱,不過會導致應用程序變大屯伞,而且會降低運行效率。不過有種情況編譯器會自動把-fobjc-arc-exceptions
標志打開劣摇,就是處于Objective-C++模式時珠移。因為C++處理異常所用的代碼與ARC實現(xiàn)的附加代碼類似,所以令ARC加入自己的代碼以安全處理異常末融,其性能損失并不太大钧惧。
第33條:以弱引用避免保留環(huán)
- 將某些引用設為weak,可避免出現(xiàn)“保留環(huán)”滑潘。
- weak引用可以自動清空垢乙,也可以不自動清空。自動清空(autonilling)是隨著ARC而引入的新特性语卤,由運行期系統(tǒng)來實現(xiàn)追逮。在具備自動清空功能的弱引用上,可以隨意讀取其數(shù)據(jù)粹舵,因為這種引用不會指向已經(jīng)回收過的對象钮孵。
第34條: 以“自動釋放池塊”降低內存峰值
- 釋放對象由兩種方式:一種是調用release方法,使其保留計數(shù)立即遞減眼滤;另一種是調用autorelease方法巴席,將其加入“自動釋放池”中。自動釋放池用于存放那些需要在稍后某個時刻釋放的對象诅需。清空(drain)自動釋放池時漾唉,系統(tǒng)會向其中的對象發(fā)送release消息荧库。
- 一般情況下無須擔心自動釋放池的創(chuàng)建問題。系統(tǒng)會自動創(chuàng)建一些線程赵刑,這些線程默認都有自動釋放池分衫,每次執(zhí)行“事件循環(huán)”(event loop)時,就會將其清空般此。通常只有一個地方需要創(chuàng)建自動釋放池蚪战,那就是在main函數(shù)里,我們用自動釋放池來包裹應用程序的主入口點(main application entry point)铐懊。
- 內存峰值(high-memory waterline)是指應用程序在某個特定時段內的最大內存用量(highest memory footproint)邀桑。
- 自動釋放池排布在棧中,對象收到autorelease消息后科乎,系統(tǒng)將其放入最頂端的池里壁畸。
- @autoreleasepool這種新式寫法能創(chuàng)建出更為輕便的自動釋放池。
第35條:用“僵尸對象”調試內存管理問題
- 向已回收的對象發(fā)送消息是不安全的茅茂。這么做有時可以瓤摧,有時不可以,可以與否玉吁,取決于對象所占內存有沒有為其他內容所覆寫照弥。而這塊內存有沒有移作他用,無法確定进副,因此應用程序只是偶爾崩潰这揣。在沒有崩潰的情況下,可能那塊內存只復用了一部分影斑,所以對象的某些二進制數(shù)據(jù)依然有效给赞。還有另外一種可能,就是那塊內存恰好為另外一個有效且存活的對象所占據(jù)矫户。在這種情況下片迅,運行期系統(tǒng)會把消息發(fā)到新對象那里,而此對象也許能應答皆辽,也許不能柑蛇。
- Xcode的“僵尸對象”(Zombie Object)調試功能打開時,運行期系統(tǒng)會把所有已經(jīng)回收的實例轉化成特殊的“僵尸對象”驱闷,而不會真正回收它們耻台。這種對象所在的核心內存無法重用,因此不可能遭到覆寫空另。僵尸對象收到消息后盆耽,會拋出異常,其中準確說明了發(fā)送過來的消息,并描述了回收之前的那個對象摄杂。僵尸對象是調試內存管理問題的最佳方式坝咐。給僵尸對象發(fā)送消息后,控制臺會打印消息析恢,應用程序則會終止畅厢。打印消息就像這樣:
*** -[CFString respondsToSelector:]: message sent to deallocated instance 0x7ff9e9c0808e0
- 系統(tǒng)在回收對象時,可以不將其真的回收氮昧,而是把它轉化為僵尸對象。通過環(huán)境變量
NSZombieEnabled
可開啟此功能浦楣。 - 系統(tǒng)會修改對象的isa指針袖肥,令其指向特殊的僵尸類(例如由原來的EOCClass變?yōu)開NSZombile_EOCClass),從而使該對象變?yōu)榻┦瑢ο笳窭汀=┦惸軌蝽憫械倪x擇子椎组,響應方式為:打印一條包含消息內容及其接收者的消息,然后終止應用程序历恐。
第36條:不要使用retainCount
- retainCount的方法之所以無用寸癌,其首要原因在于:它所返回的保留計數(shù)只是某個給定時間點上的值。該方法并未考慮到系統(tǒng)稍后會把自動釋放池清空(參見第34條)弱贼,因而不會將后續(xù)的釋放操作從返回值里減去蒸苇,這樣的話,此值就未必能真實反應實際的保留計數(shù)了吮旅。
- 引入ARC之后溪烤,retainCount方法就正式廢止了,在ARC下調用該方法會導致編譯器報錯庇勃。
===================================
第六章 塊與大中樞派發(fā)
===================================
第37條:理解“塊”這一概念
- 塊其實就是個值檬嘀,而且自有其相關類型。與int责嚷、float或Objective-C對象一樣鸳兽,也可以把塊賦給變量,然后像使用其他變量那樣使用它罕拂。塊類型的語法與函數(shù)指針近似揍异。
// 語法結構
return_type (^block_name)(parameters)
// 初始化賦值
int (^addBlock)(int a, int b) = ^(int a, int b) {
return a + b;
}
- 默認情況下,為塊所捕獲的變量爆班,是不可以在塊里修改的, 需要在聲明變量的時候加上_ _block修飾符才可以蒿秦。
- 塊本身可視為對象,有引用計數(shù)蛋济。當最后一個指向塊的引用移走之后棍鳖,塊就回收了。回收時也會釋放塊所捕獲的變量渡处,以便平衡捕獲時所執(zhí)行的保留操作镜悉。
-
塊的內部結構:
塊存放在塊對象的內存區(qū)域中,首個變量是指向Class對象的指針医瘫,該指針叫做isa侣肄。其余內存里含有塊對象正常運轉所需的各種信息。
在內存中 最重要的就是invoke變量醇份,這個函數(shù)指針稼锅,指向塊的實現(xiàn)代碼。函數(shù)原型至少要接受一個void *型的參數(shù)僚纷,此參數(shù)代表塊矩距。descriptor變量是指向結構體的指針,每個塊里都包含此結構體怖竭,其中聲明了塊對象的總體大小锥债,還聲明了copy與dispose這兩個輔助函數(shù)所對應的函數(shù)指針。輔助函數(shù)在拷貝及丟棄塊對象時運行痊臭,其中會執(zhí)行一些操作哮肚,比如說,前者要保留捕獲的對象广匙,而后者則將之釋放允趟。
塊還會把它所捕獲的所有變量都拷貝一份。這些拷貝放在descriptor變量后面鸦致,捕獲了多少個變量拼窥,就要占據(jù)多少內存空間。請注意蹋凝,拷貝的并不是對象本身鲁纠,而是指向這些對象的指針變量。invoke函數(shù)為何需要把塊對象作為參數(shù)傳進來呢鳍寂?原因在于改含,執(zhí)行塊時,要從內存中把這些捕獲到的變量讀出來迄汛。 - 塊可以分配在椇慈溃或堆上,也可以是全局的鞍爱。分配在棧上的塊可拷貝到堆里鹃觉,這樣的話,就和標準的Objective-C對象一樣睹逃,具備引用計數(shù)了盗扇。
第38條:為常用的塊類型創(chuàng)建typedef
- 以typedef重新定義塊類型祷肯,可令塊變量用起來更加簡單。
- 定義新類型時應遵守現(xiàn)有的命名習慣疗隶,勿使其名稱與別的類型相沖突佑笋。
- 不妨為同一個塊簽名定義多個類型別名。如果要重構的代碼使用了塊類型的某個別名斑鼻,那么只需修改相應typedef中的塊簽名即可蒋纬,無須改動其他typedef。
第39條:用handler塊降低代碼分散程度
- 在創(chuàng)建對象時坚弱,可以使用內聯(lián)的handler塊將相關業(yè)務邏輯一并聲明蜀备。
- 在有多個實例需要監(jiān)控時,如果采用委托模式荒叶,那么經(jīng)常需要根據(jù)傳入的對象來切換碾阁,而若改用handler塊來實現(xiàn),則可直接將塊與相關對象放在一起停撞。
- 設計API時如果用到了handler塊,那么可以增加一個參數(shù)悼瓮,使調用者可通過此參數(shù)來決定應該把塊安排在哪個隊列上執(zhí)行戈毒。
第40條:用塊引用其所屬對象時不要出現(xiàn)保留環(huán)
- 如果塊所捕獲的對象直接或間接地保留了塊本身,那么就得當心保留環(huán)問題横堡。
- 一定要找個適當?shù)臅r機解除保留環(huán)埋市,而不能把責任推給API的調用者。
第41條:多用派發(fā)隊列命贴,少用同步鎖
- 濫用“同步塊”@synchronized(self)會降低代碼效率道宅,因為共用同一個鎖的那些同步塊,都必須按順序執(zhí)行胸蛛。若是在self對象上頻繁加鎖污茵,那么程序可能要等另一段與此無關的代碼執(zhí)行完畢,才能繼續(xù)執(zhí)行當前代碼葬项。
- 使用NSLock對象泞当,在極端情況下,同步塊會導致死鎖民珍,另外效率也不見得很高襟士,而如果直接使用鎖對象的話,一旦遇到死鎖嚷量,就會非常麻煩陋桂。
- 派發(fā)隊列可用來表述同步語義(synchronization semantic),這種做法要比使用@synchronized塊或NSLock對象更簡單蝶溶。
- 將同步與異步派發(fā)結合起來嗜历,可以實現(xiàn)與普通加鎖機制一樣的同步行為,而這么做卻不會阻塞執(zhí)行異步派發(fā)的線程。
- 使用同步隊列及柵欄塊秸脱,可以令同步行為更加高效落包。
第42條:多用GCD,少用performSelector系列方法
- performSelector系列方法在內存管理方面容易疏失摊唇。它無法確定將要執(zhí)行的選擇子具體是什么咐蝇,返回什么,因而ARC編譯器也就無法插入適當?shù)膬却婀芾矸椒ā?/li>
- persormSelector系列方法所能處理的選擇子太過局限了巷查,選擇子的返回值類型及發(fā)送給方法的參數(shù)個數(shù)都受到限制有序。
- 如果想把任務放在另一個線程上執(zhí)行,那么最好不要用performSelector系列方法岛请,而是應該把任務封裝在到塊里旭寿,然后調用大中樞派發(fā)機制的相關方法來實現(xiàn)。
第43條:掌握CGD及操作隊列的使用時機
- 在解決多線程與任務管理問題時崇败,派發(fā)隊列并非唯一方案盅称。
- 操作隊列(NSOperationQueue)提供了一套高級Objective-C API,能實現(xiàn)純GCD所具備的絕大部分功能后室,而且還能完成一些更為復雜的操作缩膝,那些操作若改用GCD來實現(xiàn),則需另外編寫代碼岸霹。
第44條:通過Dispatch Group機制疾层,根據(jù)系統(tǒng)資源狀況來執(zhí)行任務
- 一些列任務可歸入一個dispatch group之中。開發(fā)者可以在這組任務執(zhí)行完畢時獲得通知贡避。
- 通過dispatch group痛黎,可以在并發(fā)式派發(fā)隊列里同時執(zhí)行多項任務。此時GCD會根據(jù)系統(tǒng)資源狀況來調度這些并發(fā)執(zhí)行的任務刮吧。開發(fā)者若自己來實現(xiàn)功能湖饱,則需編寫大量代碼厦瓢。
第45條:使用dispatch_once來執(zhí)行只需運行一次的線程安全代碼
- 經(jīng)常需要編寫“只需執(zhí)行一次的線程安全代碼”(thread-safe single-code execution)廊谓。通過GCD所提供的dispatch_once函數(shù),很容易就能實現(xiàn)此功能见转。
- 標記應該聲明在static或global作用域中水醋,這樣的話旗笔,在把只需執(zhí)行一次的塊傳給dispatch_once函數(shù)時,傳進去的標記也是相同的拄踪。
第46條:不要使用dispatch_get_current_queue(推薦看原文
)
- dispatch_get_current_queue函數(shù)的行為常常與開發(fā)者所預期的不同蝇恶。此函數(shù)已經(jīng)廢棄,只應做調試只用惶桐。
- 由于派發(fā)隊列是按層級來組織的撮弧,所以無法單用某個隊列對象來描述“當前隊列”這一概念潘懊。
- dispatch_get_current_queue函數(shù)用于解決由不可重入的代碼所引發(fā)的死鎖,然而能用此函數(shù)解決的問題贿衍,通常也能改用“隊列特定數(shù)據(jù)”來解決授舟。
===================================
第七章 系統(tǒng)框架
===================================
第47條:熟悉系統(tǒng)框架
- 系統(tǒng)框架都是動態(tài)庫
- 在為Mac OS X或iOS系統(tǒng)開發(fā)“帶圖形界面的應用程序”(graphical application)時,會用到名為Cocoa的框架贸辈,在iOS上稱為Cocoa Touch释树。其實Cocoa本身并不是框架,但是里面集成了一批創(chuàng)建應用程序時經(jīng)常用到的框架擎淤。
- 許多系統(tǒng)框架都可以直接使用奢啥。其中最重要的是Foundation與CoreFoundation,這兩個框架提供了構建應用程序所需的許多核心功能嘴拢。
- 很多常見任務都能用框架來做桩盲,例如音頻與視頻處理、網(wǎng)絡通信席吴、數(shù)據(jù)管理等赌结。
- 請記住:用純C寫成的框架與用Objective-C寫成的一樣重要孝冒,若想成為優(yōu)秀的Objective-C開發(fā)者柬姚,應該掌握C語言的核心概念。
第48條:多用塊枚舉迈倍,少用for循環(huán)
- 遍歷collection有四種方式伤靠。最基本的方法是for循環(huán)捣域,其次是NSEnumerator遍歷法及快速遍歷法啼染,最新、最先進的方式則是“塊枚舉法”焕梅。
- “塊枚舉法”本身就能通過GCD來并發(fā)執(zhí)行遍歷操作迹鹅,無須另行編寫代碼。而采用其他遍歷方式則無法輕易實現(xiàn)這一點贞言。
- 若提前知道待遍歷的collection含有何種對象斜棚,則應修改塊簽名,指出對象的具體類型该窗。
第49條:對自定義其內存管理語義的collection使用無縫橋接
- 通過無縫橋接技術弟蚀,可以在Foundation框架中的Objective-C對象與CoreFoundation框架中的C語言數(shù)據(jù)結構之間來回轉換。
- 在CoreFoundation層面創(chuàng)建collection時酗失,可以指定許多回調函數(shù)义钉,這些函數(shù)表示此collection應如何處理其元素。然后规肴,可運用無縫橋接技術捶闸,將其轉換成具備特殊內存管理語義的Objective-C collection夜畴。
第50條:構建緩存時選用NSCache而非NSDictionary
- 實現(xiàn)緩存時應選用NSCache而非NSDictionary對象。因為NSCache可以提供優(yōu)雅的自動刪減功能删壮,而且是“線程安全的”贪绘,此外,它與字典不同央碟,并不會拷貝鍵税灌。
- 可以給NSCache對象設置上限,用以限制緩存中的對象總個數(shù)及“總成本”硬耍,而這些尺度則定義了緩存刪減其中對象的時機垄琐。但是絕對不要把這些尺度當成可靠的“硬限制”(hard limit),它們僅對NSCache起指導作用经柴。
- 將NSPurgeableData與NSCache搭配使用狸窘,可實現(xiàn)自動清除數(shù)據(jù)的功能,也就是說坯认,當NSPurgeableData對象所占內存為系統(tǒng)所丟棄時翻擒,該對象自身也會從緩存中移除。
- 如果緩存使用得當牛哺,那么應用程序的響應速度就能提高陋气。只有那種“重新計算起來很費事的”數(shù)據(jù),才值得放入緩存引润,比如那些需要從網(wǎng)絡獲取或從磁盤讀取的數(shù)據(jù)巩趁。
第51條:精簡initialize與load的實現(xiàn)代碼(推薦看原文)
- 在加載階段(通常指應用程序啟動的時候),如果類實現(xiàn)了load方法淳附,那么系統(tǒng)就會調用它议慰。分類里也可以定義此方法,類的load方法要比分類中的先調用奴曙。與其他方法不同别凹, load方法不參與覆寫機制。
- load方法中使用其他類是不安全的洽糟,因為不能卻確定其他類是否已經(jīng)加載好了炉菲。
- 首次使用某個類之前,系統(tǒng)會向其發(fā)送initialize消息坤溃。由于此方法遵從普通的覆寫規(guī)則拍霜,所以通常應該在里面判斷當前初始化的是哪個類。
- load與initialize方法都應該實現(xiàn)得精簡一些薪介,這有助于保持應用程序的響應能力祠饺,也能減少引入"依賴環(huán)"(interdependency cycle)的幾率。
- 無法在編譯器設定的全局變量昭灵,可以放在initialize方法里初始化吠裆。
第52條:別忘了NSTimer會保留其目標對象(推薦看原文)
- NSTimer 對象會保留其目標伐谈,直到計時器本身失效為止,調用invalidate方法可令計時器失效试疙,另外诵棵,一次性的計時器在觸發(fā)完任務之后也會失效。
- 反復執(zhí)行任務的計時器(repeating timer)祝旷,很容易引入保留環(huán)履澳,如果這種計時器的目標對象又保留了計時器本身,那肯定會導致保留環(huán)怀跛。這種環(huán)狀保留關系距贷,可能是直接發(fā)生的,也可能是通過對象圖里的其他對象間接發(fā)生的吻谋。
- 可以擴充NSTimer的功能忠蝗,用“塊”來打破保留環(huán)。不過漓拾,除非NSTimer將來在公共接口里提供功能阁最,否則必須創(chuàng)建分類,將相關實現(xiàn)代碼加入其中骇两。