2017/12/31
第一章 熟悉Objective-C
第1條 了解Objective-C語言的起源
- Objective-C為C語言添加了面向對象的特性,是其超急。Objective-C使用動態(tài)綁定的消息結構,也就是說,在運行時才會檢查對象類型。接收一條消息后,究竟應執(zhí)行何種代碼,由運行環(huán)境而非編譯器來決定。
- 理解C語言的核心概念有助于寫好Objective-C程序。尤其要掌握內存模型。
第2條 在類的頭文件中盡量少引入其他頭文件
- 除非確有必要,否則不要進入頭文件。一般來說,應在某個類的頭文件中使用向前聲明來提及別的類,并在實現(xiàn)文件中引入那些類的頭文件。這樣做可以降低類之間的耦合。
- 有時無法使用向前聲明,比如要聲明某個類要遵守某個協(xié)議。這種情況下,盡量把“該類遵守某協(xié)議”的這條聲明移至“分類”中。如果不行的話,就把協(xié)議單獨放在一個頭文件中,然后將其引入。
第3條 多用字面量語法
- 應該使用字面量語法, 以使代碼更簡明扼要。
- 字符串 @“str” ??
- 數(shù)值 @10 ??
- 數(shù)組 @[] ??
- 字典 @{} ??
- 應使用下標來訪問數(shù)組,鍵訪問字典。
- arr[10] ??
- dict[@"name"] ??
- 字面量創(chuàng)建數(shù)組或字典時,若值中有
nil,會拋出異常。請確保值不含nil。- @[nil] ?
- @{nil} ?
第4條 多用類型常量,少用#define預處理指令
- 不要用預處理指令定義常量。這樣定義出來的常量不含類型信息,編譯器只會在編譯時做替換操作。即使有人重新定義了常量值,編譯器也不會發(fā)出警告。還有個弊端是會產(chǎn)生N多份臨時變量。
- 在實現(xiàn)文件中使用
static const來定義只在當前文件可見的常量,例如static NSString *const kAnimationName = @“greatWave” - 在頭文件中使用
extern來聲明全局常量,并在相關實現(xiàn)文件中定義值。這種常量會出現(xiàn)在全局符號中,所以其名稱應以類名做前綴,如UIAplicationDidBecomeActiveNotification
第5條 用枚舉表示狀態(tài)、選項、狀態(tài)碼
- 應該用枚舉來表示狀態(tài)機的狀態(tài)、傳遞給方法的選項以及狀態(tài)碼等值,給這些值起個易懂的名字。
- 如果值為可選項類型(可多選),那就定義值為
2^N,以便可以使用按位或操作。 - 用
NS_ENUM與NS_OPTIONS宏來定義枚舉類型,并指明底層數(shù)據(jù)類型例如NS_ENUM(NSUInteger, VLCPlayerState)。 - 在用
switch處理枚舉時不要有default分支,這樣在加入新枚舉后編譯器會提示有未處理的枚舉。
第二章 對象、消息、運行期
第6條 理解“屬性”這一概念
- 用
@property來定義對象的屬性。 - 通過“特質”來指定存儲數(shù)據(jù)所需的正確語義。
- iOS開發(fā)應該用
nonatomic,因為atomic會嚴重影響性能。
第7條 在對象內部盡量直接訪問實例變量
- 在對象內部讀取數(shù)據(jù)時,應該直接通過追李變量來讀,而寫入數(shù)據(jù)時,則應該通過屬性來寫。
- 在初始化方法及
dealloc方法總,總是應該直接通過實例變量來讀寫數(shù)據(jù)。 - 懶加載時,通過屬性來讀取數(shù)據(jù)。
第8條 理解“對象等同性”這一概念
- 檢測對象等同性應提供
isEqual與hash方法。 -
==只會對比兩個指針所指對象的地址。 - 相同對象必須具有相同
hash,但hash相同未必對象相同。 - 編寫
hash方法時,應使用計算速度快而且hash碰撞率低的算法。
第9條 以“類族模式”隱藏實現(xiàn)細節(jié)
- 類族模式可以把實現(xiàn)細節(jié)隱藏在一套簡單的公共接口后面
- 系統(tǒng)框架里的大部分collection類都是類族,例如NSArray、NSMutableArray。
第10條 在既有類中使用關聯(lián)對象存放自定義數(shù)據(jù)
- 只有在其他做法不可行時采用,否則可能引入難以查找的bug。
- 使用方法:
void setAssociatedObject(id object, void\*key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, void\*key);
void objc_removeAssociatedObjects(id object)
第11條 理解objc_msgSend的作用
void objc_msgSend(id self, SEL cmd, ...)- 舉例
id return = [git commit:parameter];
上面的Objective-C方法在運行時會轉換成如下函數(shù):
id return = objc_msgSend(git, @selector(commit), parameter);
第12條 理解消息轉發(fā)機制
第13條 用“方法調配技術”調試“黑盒方法”
第14條 理解“類對象”的用意
第三章 接口與API
第15條 用前綴避免命名沖突
第16條 提供“全能初始化方法”
- 所有初始化方法最終都會調用到這個方法,便于統(tǒng)一管理
第17條 實現(xiàn)description方法
- debugDescription方法是在開發(fā)者在調試器中以控制臺命令打印對象時調用的,如po
第18條 盡量使用不可變對象
- 盡量把對外開放的屬性設為readonly,而且可能的話盡量不要對外開放(隱藏細節(jié))。
- 不想被外部改變的屬性,對外聲明時應該使用readonly,而在內部分類中重新聲明為readwrite。
- 不要把可變的collection作為屬性公開,而應該提供相關方法,以此修改對象中的可變collection。
- 例如:
// Person.h
@interface Person:NSObject
@property (nonatomic, strong, readonly) NSSet *friends;
@end
// Person.m
@implementation Person {
NSMutableSet *_internalFriends;
}
- (NSSet *)friends {
return [_internalFriends copy];
}
- (void)addFriend:(Person *)person {
[_internalFriends addObject:person];
}
@end
第19條 使用清晰而協(xié)調的命名方式
- 盡量不要使用縮略詞。
- 如果方法返回值是新創(chuàng)建的,那么方法名的首個詞應是返回值的類型(如-intValue;+string),除非另有修飾詞(如:- lowercaseString;)。
- Boolean屬性應選用has、is當前綴。
- 將get這個前綴留給借由“輸出參數(shù)”來保存返回值的方法,如
-(void)getBytes:(uchar \*)buffer range:(Range)aRange;
第20條 為私有方法名加前綴
- 給私有方法名加上前綴,建議使用
p_,便于與公開方法區(qū)分。 - 不要使用單個下劃線
_作為私有方法名的前綴,因為這是預留給蘋果公司用的,這可能會覆蓋蘋果的私有方法。
第21條 理解Objective-C錯誤模型
- NSError封裝了三個屬性 {domain code info}
- 只有發(fā)生了嚴重錯誤時才使用異常NSException
- 小錯誤可以返回NSError給調用者處理
第22條 理解NSCopying協(xié)議
- 要實現(xiàn)
copy就要重寫- (id)copyWithZone:(NSZone \*)zone; - 要實現(xiàn)
mutableCopy就要重寫- (id)mutableCopyWithZone:(NSZone \*)zone;
(以前會把內存分成不同的zone,現(xiàn)在只有一個zone,所以不必擔心這個參數(shù)) - 復制對象時需決定深拷貝還是淺拷貝,盡量用淺拷貝。
第四章 協(xié)議與分類
第23條 通過代理與數(shù)據(jù)源協(xié)議進行對象間通信
- delegate屬性一定要是weak,否則會引入“保留環(huán)”
// 如果頻繁調用如下代碼:
if ([_delegate respondsToSelecter:@selecter(someClassDidSomething:)]) {
[_delegate someClassDidSomething];
}
// 可考慮將檢查加過緩存到本地標記:
_delegateFlags.didUpdateProgressTo = [_delegate respondsToSelecter:@selecter(someClassDidSomething:)]
...
if (_delegateFlags.didUpdateProgressTo) {
[_delegate didUpdateProgressTo:progress]
}
第24條 將類的實現(xiàn)代碼分散到便于管理的數(shù)個分類之中
- 使用分類機制把類的實現(xiàn)代碼劃分成易于管理的小塊。
- 將應該視為“私有”的方法歸入名為Private的分類中,以隱藏實現(xiàn)細節(jié)。
第25條 總是為第三方類的分類名稱加入前綴
- 添加分類的時候要注意是否會覆寫已有方法,因加載時機不確定很可能發(fā)生覆蓋或被覆蓋,這種bug很難查。
- 向第三方類中添加分類時,應該給其名稱及方法加上你的專用前綴,以減少覆寫情況。
第26條 勿在分類中聲明屬性
- 為分類添加屬性技術上可行(見第6條),但盡量避免,因為這種方法無法把實現(xiàn)屬性所需的實例變量合成出來。
- 把封裝數(shù)據(jù)所用的全部屬性都定義在主接口里。
- 在分類中可以定義存取方法,但盡量不要定義屬性。
第27條 使用匿名擴展隱藏實現(xiàn)細節(jié)
- 通過擴展向類中增加實例變量。
- 如果某屬性在主接口中聲明為readonly,那可以在擴展中重新聲明為readwrite。
若想使類遵循的下一不為人知,可在擴展中聲明。
第28條 通過協(xié)議提供匿名對象
第5章 內存管理
第29條 理解引用計數(shù)
第30條 以ARC簡化引用計數(shù)
- ARC只負責管理OC對象的內存,CF開頭的對象要開發(fā)者管理。
第31條 在dealloc方法中只釋放引用并解除監(jiān)聽
- dealloc里應該做的事是釋放指向其他對象的引用,并取消訂閱KVO或Notification等通知,不要做其他事。
- 如果持有文件秒速符等系統(tǒng)資源,就應該編寫一個方法來釋放這種資源。
- 不要在dealloc里做異步任務,因為此時對象已經(jīng)在回收狀態(tài)了。
第32條 編寫“異常安全代碼”時留意內存管理問題
- OC中拋出異常的時候可能會引起內存泄漏
- 在@try捕獲異常中清理干凈
第33條 以弱引用避免保留環(huán)
- 以弱引用避免“保留環(huán)(Retain Cycle)”
第34條 以“自動釋放池”降低內存峰值
- 自動釋放池排布在棧中。
- 自動釋放池一般放在可能引起內存峰值比較高的地方,如在for循環(huán)創(chuàng)建大量臨時變量。
- 寫法:
@autoreleasepool {
// ...
}
第35條 用“僵尸對象”調試內存管理問題
- 在Scheme->Run->Diagnostics->“Enable Zombie Objects”,勾選即可開啟此功能。
- 開啟此功能后,系統(tǒng)回收對象時不是釋放對象,而是轉為僵尸對象,僵尸對象能響應所有選擇子,響應方式為打印相關信息然后終止程序。
第36條 不要使用retainCount
- 對象的保留計數(shù)并不能絕對準確反映對象的生命期。
- 在蘋果引入ARC之后retainCount已經(jīng)正式廢棄,任何時候都不要調用這個retainCount方法來查看引用計數(shù)了,因為這個值實際上已經(jīng)沒有準確性了。但是在MRC下還是可以正常使用
塊與大中樞派發(fā)
第37條 理解“塊”這一概念
- 塊是閉包。
- 塊可以分配在棧塊、堆塊或全局塊上,要注意塊的作用域
// 不安全的塊,因為A塊、B塊都是分配在棧上的,離開了if語句可能會被釋放
void (^block)();
if (condition_1) {
block = ^{ print("A") };
} else {
block = ^{ print("B") };
}
block();
// 若將塊拷貝copy就會搬到堆上,就會變成對象,就會安全
block = [^{ print("A") } copy];
block = [^{ print("B") } copy];
第38條 為常見的塊類型創(chuàng)建typedef
- 主要是為了考慮代碼可讀性。
第39條 用handler塊降低代碼分散程度
- 異步操作中使用block的方式設計,這樣業(yè)務相關的代碼會比較緊湊,不會顯得那么凌亂。
第40條 用塊引用其所屬對象時不要出現(xiàn)保留環(huán)
- 不是一定得在block中使用weakself,block 不是被self所持有的,在block中就可以直接使用self
第41條 多用派發(fā)隊列,少用同步鎖
- 這種寫法效率很低,也不能保證線程中絕對安全:
// 1. synchronized
@synchronized(self) {
return _someString;
}
// 2. nslock
NSLock *_locker = [[NSLock alloc] init];
[_locker lock];
// safe code
[_locker unlock];
- 應該用GCD來替換:
_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;
});
}
第42條 多用GCD,少用performSelector系列方法
- performSelector有很多缺點:
- 內存管理問題:在ARC下我們經(jīng)常會看到編譯器發(fā)出如下警告:warning: performSelector may cause a leak because its selector is unknown [-Warc-performSelector-leaks];
- 返回值只能是void或對象類型;
- 無法處理帶有多個參數(shù)的選擇子,最多只能處理兩個參數(shù)
- 用dispatch系列函數(shù)代替
第43條 掌握GCD以及操作隊列的使用時機
- 需要的時候可以用NSOperation和NSOperationQueue代替GCD
- 使用NSOperation和NSOperationQueue的優(yōu)點:
- 支持取消某個操作
- 支持指定操作間的依賴關系
- 支持指定操作的優(yōu)先級
- 重用NSOperation對象
第44條 通過Dispatch Group機制,根據(jù)系統(tǒng)資源狀況來執(zhí)行任務
- DispatchGroup可將任務分組,然后等待這組任務執(zhí)行完畢時會有通知,開發(fā)者可以拿到結果然后繼續(xù)下一步操作:
// A B C將會同步執(zhí)行
dispatch_group_async({...A})
dispatch_group_async({...B})
dispatch_group_async({...C})
// 以上都執(zhí)行完再執(zhí)行notify
dispatch_group_notify({...})
- 通過dispatch group在并發(fā)隊列上同時執(zhí)行多項任務的時候,GCD會根據(jù)系統(tǒng)資源狀態(tài)來幫忙調度這些并發(fā)執(zhí)行的任務。
第45條 使用dispatch_once來執(zhí)行只需要運行一次的線程安全代碼
- dispatch_once比較高效,沒有重量級的同步機制, 常用于單例模式:
+ (instancetype)sharedInstance {
static id _instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[super allocWithZone: NULL] init];
});
return _instance;
}
+ (id)allocWithZone:(struct _NSZone *)zone {
return [self sharedInstance];
}
- (id)copyWithZone:(struct _NSZone *)zone {
return [[self class] sharedInstance];
}
- (instancetype)init {
if (self = [super init]) {
<#code#>
}
return self;
}
第46條 不要使用dispatch_get_current_queue
- dispatch_get_current_queue 函數(shù)的行為常常與開發(fā)者所預期的不同,iOS 6.0起此函數(shù)已經(jīng)廢棄,只應做調試之用。
第7章 系統(tǒng)框架
第47條 熟悉系統(tǒng)框架
- Foundation OC基礎框架,其中的類以NS為前綴
- CoreFoundation C語言框架,CF前綴,可與Foundation無縫橋街
- AVFoundation OC對象可用來回訪并錄制音頻及視頻。
- CFNetwork C語言級別的網(wǎng)絡通信能力。
- CoreAudio C語言API可以用來操作設備上的音頻硬件。
- CoreData OC接口可以將對象放入數(shù)據(jù)庫,將數(shù)據(jù)持久化。
- CoreText C語言接口可以高效執(zhí)行文字排版以及渲染操作。
- SpriteKit 游戲框架
- CoreLocation、MapKit 定位地圖相關框架
- Social 社交網(wǎng)絡框架
- AddressBook 需要使用通訊錄時才使用該框架
- MusicLibraries 音樂庫相關框架
- HealthKit 健康相關框架
- HomeKit 為智能化硬件提供的框架
- CloudKit iCloud相關的框架
- NSLinguisticTagger 解析自然語言,如動詞,名詞等
- UIKit 構建在基礎框架之上,包含UI元素和粘合機制
- CoreAnimation 是QuartzCore框架的一部分,OC級別的核心動畫庫,用以圖形渲染
- CoreGraphics C語言級別2D渲染所必備的數(shù)據(jù)結構與函數(shù),如CGRect、CGPoint
第48條 多用塊枚舉,少用for循環(huán)
- 遍歷有4方法:for、NSEnumerator、for—in、塊枚舉
- 塊枚舉法是通過GCD來并發(fā)執(zhí)行遍歷操作,當屬最高效方法。
- 若提前知道待遍歷的collection含有何種對象,則應修改塊簽名,指出對象的具體類型。
- 例如字典遍歷:
[dict enumeratKeysAndObjectsUsingBlock: ^(NSString \*key, NSString \*obj){
// ...
}];
第49條 對自定義其內存管理語義的collecion使用無縫橋接
- 通過無縫橋接技術,可以在定義于Foundation框架中的類和CoreFoundation框架中的C語言數(shù)據(jù)結構之間來回轉換。
下面代碼展示了簡單的無縫橋接:
NSArray *anNSArray = @[@1, @2, @3, @4, @5];
CFArrayRef aCFArray = (__bridge CFArrayRef)anNSArray;
NSLog(@"Size of array = %li", CFArrayGetCount(aCFArray));
//Output: Size of array = 5
轉換操作中的__bridge告訴ARC如何處理轉換所涉及的OC對象,也就是ARC仍然具備這個OC對象的所有權。__bridge_retained則意味著ARC將交出所有權。
- 使用
CFRelease(aCFArray)釋放對象。
第50條 構建緩存時選用NSCache而非NSDictionary
- NSCache會在系統(tǒng)資源緊急時自動釋放緩存。
- NSCache是線程安全的。
- NSCache會先行釋放最久未使用的對象。
- 只有重新計算、獲取開銷很大的數(shù)據(jù)才值得緩存,如網(wǎng)絡獲取,磁盤讀取。
第51條 精簡initialize與load的實現(xiàn)代碼
- oad與initialize 方法都應該實現(xiàn)的精簡一點,這樣有助于保持應用程序的響應能力,也可以減少引入依賴環(huán)的幾率
第52條 別忘了NSTimer會保留其目標對象
- 在iOS開發(fā)中經(jīng)常會用到定時器:NSTimer,由于NSTimer會生成指向其使用者的引用,而其使用者如果也引用了NSTimer,那就形成了該死的循環(huán)引用。