一篇文章拿下《Effective Objective C 2 0編寫高質量iOS與OS X代碼的52個有效方法》

最近在重溫這本OC經典之做《Effective Objective-C 2.0編寫高質量iOS與OS X代碼的52個有效方法》,這篇文章算是重溫以後的產物吧,讀完這篇文章你將快速讀完這本書,因爲我的能力有限,不免有一些遺漏或者錯誤,請各位看官不吝賜教!謝謝!同時若是有任何問題也能夠在下方留言,歡迎一塊兒交流進步!另外因爲篇幅緣由,書中一些基礎知識的介紹文中就省略掉了。php

目錄
上面就是這本書的目錄,能夠 點擊這裏下載PDF版,原版英文版PDF我也有存~


第一章:熟悉Objective-C

第一條:瞭解Objective-C語言的起源

  1. Objective-C從Smalltalk語言是從Smalltalk語言演化而來, Smalltalk是消息語言的鼻祖。
  2. Objective-CC語言的超集,在C語言基礎上添加了面向對象等特性,可能一開始接觸時你會以爲語法有點奇怪,那是由於Objective-C使用了動態綁定的消息結構,而JavaC++等等語言使用的是函數調用。
  3. 消息結構函數調用的關鍵區別在於:函數調用的語言,在編譯階段由編譯器生成一些虛方法表,在運行時從這個表找到所要執行的方法去執行。而使用了動態綁定的消息結構在運行時接到一條消息,接下來要執行什麼代碼是運行期決定的,而不是編譯器。

第二條: 在類的文件中儘可能少引用其餘頭文件

  1. 若是須要引用一個類文件時,只是須要使用類名,不須要知道其中細節,能夠用@class xx.h,這樣作的好處會減小必定的編譯時間。若是是用的#import所有導入的話,會出現a.h import了b.h,當c.h 又import a.h時,把b.h也都導入了,若是隻是用到類名,真的比較浪費,也不夠優雅
  2. 有時候沒法使用@class向前聲明,好比某個類要遵循一項協議,這個協議在另一個類中聲明的,能夠將協議這部分單獨放在一個頭文件,或者放在分類當中,以下降引用成本。

第三條:多用字面量語法,少用與之等價的方法

1.多使用字面量語法來建立字符串,數組,字典等。 傳統建立數組方法:git

NSArray *languages = [NSArray arrayWithObjects:@"PHP", @"Objective-C", someObject, @"Swift", @"Python", nil];
NSString *Swift = [languages objectAtIndex:2];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:@"key", @"value", nil];
NSString *value = [languages objectForKey:@"key"];
複製代碼

字面量:github

NSArray *languages = @[@"PHP", @"Objective-C", someObject, @"Swift", @"Python"];
NSString *Swift = languages[2];
NSDictionary *dict = @{@"key" : @"value"};
NSString *value = languages[@"key"];

複製代碼

這樣作的好處:使代碼更簡潔,易讀,也會避免nil問題。好比languages數據中 someObject 若是爲nil時,字面量語法就會拋出異常,而使用傳統方法建立的languages數組值確是@[@"PHP", @"Objective-C"];由於字面量語法實際上是一種語法糖,效果是先建立了一個數組,而後再把括號中的對象都加到數組中來。 不過字面量語法有一個小缺點就是建立的數組,字符串等等對象都是不可變的,若是想要可變的對象須要本身多執行一步mutableCopy,例如數據庫

NSMutableArray *languages = [@[@"PHP", @"Objective-C", @"Swift", @"Python"] mutableCopy];
複製代碼

第四條:多用類型常量,少用#define預處理指令

第4條第5條看這裏編程

第五條:多用枚舉表示狀態、選項、狀態碼

第4條第5條看這裏數組


第二章:對象、消息、運行期

第六條:理解「屬性」這一律念

這一條講的是屬性的基本概念,以及屬性的各類修飾符,這些就很少囉嗦了,這裏強調一下:緩存

  1. 定義對外開放的屬性時候儘可能作到暴露權限最小化,不但願被修改的屬性要加上readonly
  2. atomic 並不能保證多線程安全,例如一個線程連續屢次讀取某個屬性的值,而同時還有別的線程在修改這個屬性值得時候,也仍是同樣會讀到不一樣的值。atomic 的原理只是在 setter and getter 方法中加了一個@synchronized(self),因此iOS開發中屬性都要聲明爲nonatomic,由於atomic嚴重影響了性能,可是在Mac OSX上開發卻一般不存在這個性能問題
  3. 說一下下面的哪一個屬性聲明有問題
@property (nonatomic, strong) NSArray *arrayOfStrong;
@property (nonatomic, copy) NSArray *arrayOfCopy;
@property (nonatomic, strong) NSMutableArray *mutableArrayOfStrong;
@property (nonatomic, copy) NSMutableArray *mutableArrayOfCopy;
複製代碼

具體運行示例點擊查看 答案是正常應該這樣聲明安全

@property (nonatomic, copy) NSArray *arrayOfCopy;
@property (nonatomic, strong) NSMutableArray *mutableArrayOfStrong;
複製代碼

第七條:在對象內部儘可能直接訪問實例變量

  1. 在類內讀取屬性的數據時,應該經過直接實例變量來讀,這樣不通過Objecit-C的方法派發,編譯器編譯後的代碼結果是直接訪問存實例變量的那塊內存中的值,而不會生成走方法派發的代碼,這樣的速度會更快。
  2. 給屬性寫入數據時,應該經過屬性的方式來寫入,這樣會調用setter 方法。可是在某種狀況下初始化方法以及dealloc方法中,老是應該直接經過實例變量來讀寫數據,這樣作是爲了不子類複寫了setter方法形成的異常。
  3. 使用了懶加載的屬性,應該一直保持用屬性的方式來讀取寫入數據。

第八條:理解「對象等同性」這一律念

思考下面輸出什麼?bash

NSString *aString = @"iphone 8";
    NSString *bString = [NSString stringWithFormat:@"iphone %i", 8];
    NSLog(@"%d", [aString isEqual:bString]);
    NSLog(@"%d", [aString isEqualToString:bString]);
    NSLog(@"%d", aString == bString);
複製代碼

答案是110 ==操做符只是比較了兩個指針,而不是指針所指的對象服務器

第九條:以「類族模式」隱藏實現細節

爲何下面這段if 永遠爲false

id maybeAnArray = @[];
    if ([maybeAnArray class] == [NSArray class]) {
         //Code will never be executed
    }
複製代碼

由於[maybeAnArray class] 的返回永遠不會是NSArray,NSArray是一個類族,返回的值一直都是NSArray的實體子類。大部分collection類都是某個類族中的’抽象基類’ 因此上面的if想要有機會執行的話要改爲

id maybeAnArray = @[];
    if ([maybeAnArray isKindOfClass [NSArray class]) {
         //Code probably be executed
    }
複製代碼

這樣判斷的意思是,maybeAnArray這個對象是不是NSArray類族中的一員 ** 使用類族的好處:能夠把實現細節隱藏再一套簡單的公共接口後面 **

第十條:在既有類中使用關聯對象存放自定義數據

這條講的是objc_setAssociatedObjectobjc_getAssociatedObject,如何使用在這裏就很少說了。值得強調的一點是,用關聯對象可能會引入難於查找的bug,畢竟是在runtime階段,因此可能要看狀況謹慎選擇

第十一條:理解「objc_msgSend」的做用

以前在瞭解Objective-C語言的起源有提到過,Objective-C是用的消息結構。這條就是讓你理解一下怎麼傳遞的消息。

  1. 在Objective-C中,若是向某個對象傳遞消息,那就會在運行時使用動態綁定(dynamic binding)機制來決定須要調用的方法。可是到了底層具體實現,倒是普通的C語言函數實現的。這個實現的函數就是objc_msgSend,該函數定義以下:
void objc_msgSend(id self, SEL cmd, ...) 
複製代碼

這是一個參數個數可變的函數,第一參數表明接收者,第二個參數表明選擇子(OC函數名),後續的參數就是消息(OC函數調用)中的那些參數 2. 舉例來講:

id return = [git commit:parameter];
複製代碼

上面的Objective-C方法在運行時會轉換成以下函數:

id return = objc_msgSend(git, @selector(commit), parameter);
複製代碼

objc_msgSend函數會在接收者所屬的類中搜尋其方法列表,若是能找到這個跟選擇子名稱相同的方法,就跳轉到其實現代碼,往下執行。如果當前類沒找到,那就沿着繼承體系繼續向上查找,等找到合適方法以後再跳轉 ,若是最終仍是找不到,那就進入消息轉發的流程去進行處理了。 3. 說過了OC的函數調用實現,你會以爲消息轉發要處理不少,尤爲是在搜索上,幸運的是objc_msgSend在搜索這塊是有作緩存的,每一個OC的類都有一塊這樣的緩存,objc_msgSend會將匹配結果緩存在快速映射表(fast map)中,這樣以來這個類一些頻繁調用的方法會出如今fast map 中,不用再去一遍一遍的在方法列表中搜索了。 4. 還有一個有趣的點,就是在底層處理髮送消息的時候,有用到尾調用優化,大概原理就是在函數末尾調用某個不含返回值函數時,編譯器會自動的不在棧空間上從新進行分配內存,而是直接釋放全部調用函數內部的局部變量,而後直接進入被調用函數的地址。

第十二條:理解消息轉發機制

關於這條這看看這篇文章:iOS理解Objective-C中消息轉發機制附Demo

第十三條:用「方法調配技術」調試「黑盒方法」

這條講的主要內容就是 Method Swizzling,經過運行時的一些操做能夠用另一份實現來替換掉原有的方法實現,每每被應用在向原有實現中添加新功能,好比擴展UIViewController,在viewDidLoad裏面增長打印信息等。具體例子能夠點擊我查看

第十四條:理解「類對象」的用意

Objective-C類是由Class類型來表示的,它其實是一個指向objc_class結構體的指針。它的定義以下:

typedef struct objc_class *Class;
複製代碼

在<objc/runtime.h>中能看到他的實現:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;    ///< 指向metaClass(元類)

#if !__OBJC2__
        Class super_class                       OBJC2_UNAVAILABLE;  ///< 父類
        const char *name                        OBJC2_UNAVAILABLE;  ///< 類名
        long version                            OBJC2_UNAVAILABLE;  ///< 類的版本信息,默認爲0
        long info                               OBJC2_UNAVAILABLE;  ///< 類信息,供運行期使用的一些位標識
        long instance_size                      OBJC2_UNAVAILABLE;  ///< 該類的實例變量大小
        struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  ///< 該類的成員變量鏈表
        struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  ///< 方法定義的鏈表
        struct objc_cache *cache                OBJC2_UNAVAILABLE;  ///< 方法緩存
        struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  ///< 協議鏈表
#endif

} OBJC2_UNAVAILABLE;
複製代碼

此結構體存放的是類的「元數據」(metadata),例如類的實例實現了幾個方法,具有多少實例變量等信息。 這裏的isa指針指向的是另一個類叫作元類(metaClass)。那什麼是元類呢?元類是類對象的類。也能夠換一種容易理解的說法:

  1. 當你給對象發送消息時,runtime處理時是在這個對象的類的方法列表中尋找
  2. 當你給類發消息時,runtime處理時是在這個類的元類的方法列表中尋找

咱們來看一個很經典的圖來加深理解:

能夠總結爲下:

  1. 每個Class都有一個isa指針指向一個惟一的Meta Class
  2. 每個Meta Classisa指針都指向最上層的Meta Class,這個Meta ClassNSObject的Meta Class。(包括NSObject的Meta Classisa指針也是指向的NSObject的Meta Class,也就是本身,這裏造成了個閉環)
  3. 每個Meta Classsuper class指針指向它本來ClassSuper Class的Meta Class (這裏最上層的NSObject的Meta Classsuper class指針仍是指向本身)
  4. 最上層的NSObject Class的super class指向 nil

第三章:接口與API設計

第十五條:用前綴避免命名空間衝突

Objective-C沒有相似其餘語言那樣的命名空間機制(namespace),好比說PHP中的

<?php
namespace Root\Sub\subnamespace;
複製代碼

這就會致使當你不當心實現了兩個相同名字的類,或者把兩個相對獨立的庫導入項目時而他們又剛好有重名的類的時候該類所對應的符號和Meta Class符號定義了兩次。因此很容易產生這種命名衝突,讓程序的連接過程當中出現出現重複的符號形成報錯。 爲了不這種狀況,咱們要儘可能在類名,以及分類和分類方法上增長前綴,還有一些宏定義等等根據本身項目來定吧

第十六條:提供「全能初始化方法」

若是建立類的實例的方式不止一種,那麼這個類就會有多個初始化方法,這樣作很好,不過仍是要在其中選定一個方法做爲全能初始化方法,剩下的其他的初始化方法都要調用它,這樣作的好處是之後若是初始化的邏輯更改了只需更改一處便可,或者是交給子類覆寫的時候也只覆寫這一個方法便可~ 舉個例子來講:能夠看一下NSDate的實如今NSDate.h中NSDate類中定義了一個全能初始化方法:

- (instancetype)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)ti NS_DESIGNATED_INITIALIZER;
複製代碼

其他的相似初始化方式定義在NSDate (NSDateCreation) 分類中

- (instancetype)initWithTimeIntervalSinceNow:(NSTimeInterval)secs;
- (instancetype)initWithTimeIntervalSince1970:(NSTimeInterval)secs;
- (instancetype)initWithTimeInterval:(NSTimeInterval)secsToBeAdded sinceDate:(NSDate *)date;
複製代碼

在NSDate文檔中有一條:If you want to subclass NSDate to obtain behavior different than that provided by the private or public subclasses, you must do these things:而後其中要作的有一步就是

Override [initWithTimeIntervalSinceReferenceDate:
](apple-reference-documentation://hcslylvSCo), one of the designated initializer methods`
複製代碼

這個是咱們組織代碼過程當中應該學習的地方!

第十七條:實現description方法

這條講的是能夠經過覆寫description方法或者debugDescription方法來在NSLog打印時或者LLDB打印時輸出更多的自定義信息。(數據和字典的能夠經過覆寫descriptionWithLocale:方法) 友情提示:不要在description中使用 NSLog("%@",self);,否則會掉進無底深淵啊 這裏我有一個有趣的想法,不過還沒徹底實現,就是想經過覆寫description能把任何一個對象的屬性值名稱,屬性值都一一完整的記錄下來,能夠點擊查看

第十八條:儘可能使用不可變對象

這條主要講儘可能使用不可變的對象,也就是在對外屬性聲明的時候要儘可能加上readonly修飾,默認是readwrite,這樣一來,在外部就只能讀取該數據,而不能修改它,使得這個類的實例所持有的數據更加安全。若是外部想要修改,能夠提供方法來進行修改。 不要把可變的collection做爲屬性公開,而應提供相關方法,以此修改對象中的可變collection(這條我的感受通常在經常使用、重要的類纔有必要,畢竟也增長了很多代碼量) 好比例子:

//Language.h
@property (nonatomic, strong) NSSet *set;
複製代碼

應該改成

//Language.h
@property (nonatomic, strong, readonly) NSSet *languages;
- (void)addLanguage:(NSString *)language;
- (void)removeLanguage:(NSString *)language;
//**.m
@implementation Language {
    NSMutableSet *mutableLanguages;
}
- (NSSet *)languages {
    return [_mutableLanguages copy];
}
- (void)addLanguage:(NSString *)language {
    [_mutableLanguages addObject:language];
}
- (void)removeLanguage:(NSString *)language {
     [_mutableLanguages removeObject:language];
}
複製代碼

第十九條:使用清晰而協調的命名方式

這條不用太強調了,具體也能夠參照一下我以前擬的Objective-C編程規範及建議,後續可能會不斷補充更新

第二十條:爲私有方法名加前綴

這條講的是應該爲類內的私有方法增長前綴,以便區分,這個感受因人而異吧,感受只要你不隨便把私有方法暴露在.h文件都能接受,曾遇到過這樣的同事,感受其不太適合寫程序吧。

第二十一條:理解Objective-C錯誤模型

不少語言都有異常處理機制,Objective-C也不例外,Objective-C也有相似的@throw,不過在OC中使用@throw可能會致使內存泄漏,多是它被設計的使用場景的問題。建議@throw只用來處理嚴重錯誤,也能夠理解爲致命錯誤(fatal error),那麼處理通常錯誤的時候(nonfatal error)時可使用NSError。

第二十二條:理解NSCopying協議

在OC開發中,使用對象時常常須要拷貝它,咱們會經過copy/mutbleCopy來完成。若是想讓本身的類支持拷貝,那必需要實現NSCopying協議,只須要實現一個方法:

- (id)copyWithZone:(NSZone*)zone
複製代碼

固然若是要求返回對象是可變的類型就要用到NSMutableCopying協議,相應方法

- (id)mutableCopyWithZone:(NSZone *)zone
複製代碼

在拷貝對象時,須要注意拷貝執行的是淺拷貝仍是深拷貝。深拷貝在拷貝對象時,會將對象的底層數據也進行了拷貝。淺拷貝是建立了一個新的對象指向要拷貝的內容。通常狀況應該儘可能執行淺拷貝。


第四章:協議與分類

第二十三條:經過委託與數據源協議進行對象間通訊

這條講的也比較基礎,就是基本的delegate,protocal使用。 有一點稍微說一下:當某對象須要從另一個對象中獲取數據時,可使用委託模式,這種用法常常被稱爲「數據源協議」(Data source Protocal)相似 UITableviewUITableViewDataSource 另外在Swift中有一個很重要的思想就是面向協議編程。固然OC中也能夠用協議來下降代碼耦合性,必要的時候也能夠替代繼承,由於遵循同一個協議的類能夠是任何,沒必要是同一個繼承體系下。

第二十四條:將類的實現代碼分散到便於管理的數個分類之中

這條主要說的是經過分類機制,能夠把類分紅不少歌易於管理的小塊。也是有一些前提的吧,多是這個類業務比較複雜,須要瘦身,須要解耦等等。做者還推薦把私有方法統一放在Private分類中,以隱藏實現細節。這個我的以爲視狀況而定吧。

第二十五條:老是爲第三方類的分類名稱加前綴

向第三方類的分類名稱加上你專用的前綴,這點沒必要多說,😜

第二十六條:勿在分類中聲明屬性

不要在分類中聲明屬性,除了「class-continuation」分類中。那什麼是「class-continuation」分類呢,其實就是咱們常常在.m文件中用到的,例如:

//Swift.m 
@interface Swift () 
//這個就是「class-continuation」分類
@end
@implementation Swift
@end
複製代碼

第二十七條:使用「class-continuation」分類隱藏實現細節

這條跟以前的也有點重複,最終目的仍是要儘可能在公共接口中向外暴露的內容最小化,隱藏實現細節,只告訴怎麼調用,怎麼使用便可。具體實現以及屬性的可修改權限盡量的隱藏掉。

第二十八條:經過協議提供匿名對象

  1. 協議能夠在某種程度上提供匿名對象,例如id<someProtocal> object。object對象的類型不限,只要能聽從這個協議便可,在這個協議裏面定義了這個對象所應該實現的方法。
  2. 若是具體類型不重要,重要的是對象可否處理好一些特定的方法,那麼就可使用這種協議匿名對象來完成。

第五章:內存管理

第二十九條:理解引用計數

  1. 理解引用計數這個能夠經過《Objective-C 高級編程》這本書中的例子來理解,比較直觀,大概以下:

對照明設備所作的工做 對OC對象所作的動做
開燈 生成對象
須要照明 持有
不須要照明 釋放
關燈 廢棄
內存管理的思考方式 對應OC方法
本身生成的對象,本身所持有 alloc/new/copy/mutableCopy等
非本身生成的對象(好比[NSArray array]),本身也能持有 retain
再也不須要本身持有的對象時釋放 release
當對象不被任何其餘對象持有時廢棄 dealloc
  1. 自動釋放池: 能夠看到在咱們程序中入口文件main.m中main函數中就包裹了一層autoreleasepool
int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([HSAppDelegate class]));
    }
}
複製代碼

autoreleasepool能夠延長對象的生命期,使其在跨越方法調用邊界後依然能夠存活一段時間,一般是在下一次「時間循環」(event loop)時釋放,不過也可能會執行的早一點。 3. 保留環: 也稱retain cycle,就是循環引用。造成緣由就是對象之間相互用強引用指向對方,會使得所有都沒法得以釋放。解決方案一般是使用弱引用(weak reference)

第三十條:以ARC簡化引用計數

使用ARC,能夠省略對於引用計數的操做,因此在ARC下調用對象的retain,release,autorelease,dealloc方法時系統會報錯。 這裏要注意CoreFoundation 對象不歸ARC管理,開發中若是有用到仍是要誰建立誰釋放,適時調用CFRetain/CFRelease。

第三十一條:在delloc方法中只釋放引用並解除監聽

不要在delloc方法中調用其餘方法,尤爲是須要異步執行某些任務又要回調的方法,這樣的很危險的行爲,極可能異步執行完回調的時候該對象已經被銷燬了,這樣就沒得玩了,crash了。 在delloc方法裏應該製做一些釋放相關的事情,包括不限於一些KVO取消訂閱,remove 通知等。

第三十二條:編寫「異常安全代碼」時留意內存管理問題

這條有點重複,以前已經說過了,OC中拋出異常的時候可能會引發內存泄漏,注意一下使用的時機,或者注意在@try捕獲異常中清理乾淨。

第三十三條:以弱引用避免保留環

這條比較簡單,內容主旨就是標題:以弱引用避免保留環(Retain Cycle)

第三十四條:以「@autoreleasepool」下降內存峯值

在遍歷處理一些大數組或者大字典的時候,可使用自動釋放池來下降內存峯值,例如:

NSArray *people = /*一個很大的數組*/
NSMutableArray *employeesArray = [NSMutableArray new];
for (NSStirng *name in people) {
    @autoreleasepool {
        MLEmployee *employee = [MLEmployee alloc] initWithName:name];
        [employeesArray addObject:employee];
    }
}
複製代碼

第三十五條:用「殭屍對象」調試內存管理問題

如上圖,勾選這裏能夠開啓殭屍對象設置。開啓以後,系統在回收對象時,不將其真正的回收,而是把它的isa指針指向特殊的殭屍類,變成殭屍對象。殭屍類可以響應全部的選擇子,響應方式爲:打印一條包含消息內容以及其接收者的消息,而後終止應用程序

第三十六條:不要使用retainCount

在蘋果引入ARC以後retainCount已經正式廢棄,任什麼時候候都不要調用這個retainCount方法來查看引用計數了,由於這個值實際上已經沒有準確性了。可是在MRC下仍是能夠正常使用


第六章:Block與GCD

第三十七條:理解block

根據block在內存中的位置,block被分紅三種類型:

  1. NSGlobalBlock 全局塊: 這種塊運行時無需獲取外界任何狀態,塊所使用的內存區域在編譯器就能夠徹底肯定,因此該塊聲明在全局內存中。若是全局塊執行copy會是一個空操做,至關於什麼都沒作。全局塊例如:
void (^block)() = ^{
    NSLog(@"I am a NSGlobalBlock");
}
複製代碼
  1. NSStackBlock 棧塊: 棧塊保存於棧區,超出變量做用域,棧上的block以及__block變量都會被銷燬。例如:
NSString *name = @"PHP";
void (^block)() = ^{
    NSLog(@"世界上最好的編程語言是%@", name);
};
NSLog(@"%@", block);
複製代碼

運行下你會發現控制檯打印的是:

<__NSStackBlock__: 0x7fff5480fa18>
複製代碼

什麼,你說什麼,你打印出來的是__ NSMallocBlock __? 那是由於你在ARC下編譯的,ARC下編譯器編譯時會幫你優化自動幫你加上了copy操做,你能夠用-fno-objc-arc關閉ARC再看一下 3. NSMallocBlock 堆塊: NSMallocBlock心裏獨白:我已經被暴露了,爲何要最後才介紹我!! 堆block內存保存於堆區,在變量做用域結束時不受影響。經過以前在ARC下的輸出已經看到了__ NSMallocBlock __.因此咱們在定義block類型的屬性時經常加上copy修飾,這個修飾實際上是多餘的,系統在ARC的時候已經幫咱們作了copy,可是仍是建議寫上copy。

第三十八條:爲經常使用的塊類型建立typedef

這條主要是爲了代碼更易讀,也比較重要。

- (void)getDataWithHost:(NSString *)host success:(void (^)(id responseDic))success;
//以上要改爲下面這種
typedef void (^SuccessBlock)(id responseDic);
- (void)getDataWithHost:(NSString *)host success:(SuccessBlock)success;
複製代碼

第三十九條:用handler塊下降代碼分散程度

在iOS開發中,咱們常常須要異步執行一些任務,而後等待任務執行結束以後通知相關方法。實現此需求的作法不少,好比說有些人可能會選擇用委託協議。那麼在這種異步執行一些任務,而後等待執行結束以後調用代理的時候,可能代碼就會比較分散。當多個任務都須要異步,等等就顯得比較不那麼合理了。 因此咱們能夠考慮使用block的方式設計,這樣業務相關的代碼會比較緊湊,不會顯得那麼凌亂。

第四十條:用塊引用其所屬對象是不要出現保留環

這點比較基礎了,可是要稍微說一下,不是必定得在block中使用weakself,好比下面:

[YTKNetwork requestBlock:^(id responsObject) {
      NSLog(@"%@",self.name);
  }];
複製代碼

block 不是被self所持有的,在block中就可使用self

第四十一條:多用派發隊列,少用同步鎖

在iOS開發中,若是有多個線程要執行同一份代碼,咱們可能須要加鎖來實現某種同步機制。有人可能第一印象想到的就是@synchronized(self),例如:

- (NSString*)someString {
    @synchronized(self) {
        return _someString;
    }
}
- (void)setSomeString:(NSString*)someString {
     @synchronized(self) {
        _someString = someString;
    }
}
複製代碼

這樣寫法效率很低,並且也不能保證線程中以爲的安全。若是有不少屬性,那麼每一個屬性的同步塊都要等其餘同步塊執行完畢才能執行。 應該用GCD來替換:

_syncQueue = dispatch_queue_create("syncQueue", DISPATCH_QUEUE_CONCURRENT);

//讀取字符串
- (NSString*)someString {
    __block NSString *localSomeString;
     dispatch_sync(_syncQueue, ^{
        localSomeString = _someString;
    });
     return localSomeString;
}
- (void)setSomeString:(NSString*)someString {
     dispatch_barrier_async(_syncQueue, ^{
        _someString = someString;
    });
}
複製代碼

第四十二條:多用GCD,少用performSelector系列方法

Objective-C本質上是一門分廠動態的語言,開發者在開發中能夠指定任何一個方法去調用,也能夠延遲調用一些方法,或者指定運行方法的線程。通常咱們會想到performSelector,可是在GCD出來以後基本就沒那麼須要performSelector了,performSelector也有不少缺點:

  1. 內存管理問題:在ARC下使用performSelector咱們常常會看到編譯器發出以下警告:warning: performSelector may cause a leak because its selector is unknown [-Warc-performSelector-leaks]
  2. performSelector的返回值只能是void或對象類型。
  3. performSelector沒法處理帶有多個參數的選擇子,最多隻能處理兩個參數。 爲了改變這些,咱們能夠用下面這種方式
dispatch_async(dispatch_get_main_queue(), ^{
        [self doSomething];
});
複製代碼

替換掉

[self performSelectorOnMainThread:@selector(doSomething) 
                       withObject:nil 
                    waitUntilDone:NO];
複製代碼

而後還能夠用

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];
});
複製代碼

替換

[self performSelector:@selector(doSomething) 
           withObject:nil 
           afterDelay:5.0];
複製代碼

第四十三條:掌握GCD以及操做隊列的使用時機

GCD技術確實很棒,可是也有一些侷限性,或者說有一些場景並不適合。好比過想取消隊列中的某個操做,或者須要後臺執行任務。還有一種技術叫NSOperationQueue,其實NSOperationQueue跟GCD有不少相像之處。NSOperationQueue在GCD以前就已經有了,GCD就是在其某些原理上構建的。GCD是C層次的API,而NSOperation是重量級的Objective-C對象。 使用NSOperationNSOperationQueue的優勢:

  1. 支持取消某個操做:在運行任務前,能夠在NSOperation對象上調用cancel方法,用以代表此任務不須要執行。不過已經啓動的任務沒法取消。GCD隊列是沒法取消的,GCD是「安排好以後就無論了(fire and forget)」。
  2. 支持指定操做間的依賴關係:一個操做能夠依賴其餘多個操做,例如從服務器下載並處理文件的動做能夠用操做來表示,而在處理其餘文件以前必須先下載「清單文件」。然後續的下載工做,都要依賴於先下載的清單文件這一操做。這時若是操做隊列容許併發執行的話,後續的下載操做就能夠在他依賴的下載清單文件操做執行完畢以後開始同時執行。
  3. 支持經過KVO監控NSOperation對象的屬性:能夠經過isCancelled屬性來判斷任務是否已取消,經過isFinished屬性來判斷任務是否已經完成等等。
  4. 支持指定操做的優先級:操做的優先級表示此操做與隊列中其餘操做之間的優先關係,優先級搞的操做先執行,優先級低的後執行。GCD的隊列也有優先級,不過不是針對整個隊列的。
  5. 重用NSOperation對象。在開發中你可使用NSOperation的子類或者本身建立NSOperation對象來保存一些信息,能夠在類中定義方法,使得代碼可以屢次使用。沒必要重複本身。

第四十四條:經過Dispatch Group機制,根據系統資源情況來執行任務

這條主要是介紹dispatch group,任務分組的功能。他能夠把任務分組,而後等待這組任務執行完畢時會有通知,開發者能夠拿到結果真後繼續下一步操做。 另外經過dispatch group在併發隊列上同時執行多項任務的時候,GCD會根據系統資源狀態來幫忙調度這些併發執行的任務。

第四十五條:使用dispatch_once來執行只須要運行一次的線程安全代碼

這條講的是經常使用的dispatch_once

+ (id)sharedInstance {
     static EOCClass *sharedInstance = nil;
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
            sharedInstance = [[self alloc] init];
    });
     return sharedInstance;
}
複製代碼

dispatch_once比較高效,沒有重量級的同步機制。

第四十六條:不要使用dispatch_get_current_queue

  1. dispatch_get_current_queue 函數的行爲經常與開發者所預期的不一樣,此函數已經廢棄,只應作調試之用。
  2. 因爲GCD是按層級來組織的,因此沒法單用某個隊列對象來描述"當前隊列"這一律念。
  3. dispatch_get_current_queue 函數用於解決由不能夠重入的代碼所引起的死鎖,而後能用此函數解決的問題,一般也能夠用"隊列特定數據"來解決。

第七章:系統框架

第四十七條:熟悉系統框架

在Objective-C中除了Foundation 與CoreFoundation以外還有不少系統庫,其中包括但不限於下面列出的這些:

  1. CFNetwork:此框架提供了C語言級別的網絡通訊能力,它將BSD socket抽象成了易於使用的網絡接口。而Foundation則將該框架裏的部份內容封裝爲Objective-C接口,以便進行網絡通訊。
  2. CoreAudio:此框架所提供的C語言API能夠用來操做設備上的音頻硬件。
  3. AVFoundation:此框架所提供的Objective-C對象可用來回訪並錄製音頻及視頻,好比可以在UI視圖類裏播放視頻。
  4. CoreData:此框架所提供的Objective-C接口能夠將對象放入數據庫,將數據持久化。
  5. CoreText:此框架提供的C語言接口能夠高效執行文字排版以及渲染操做。
  6. SpriteKit :遊戲框架
  7. CoreLocation、MapKit :定位地圖相關框架
  8. Address Book框架:須要使用通信錄時才使用該框架
  9. Music Libraries框架:音樂庫相關框架
  10. HealthKit框架:健康相關框架
  11. HomeKit框架:爲智能化硬件提供的框架
  12. CloudKit : iCloud相關的框架
  13. Passbook、PassKit框架:爲了在應用中用戶能夠很容易的訪問他們以前購買的活動門票、旅行車票、優惠券等等提供的框架

第四十八條:多用塊枚舉,少用for循環

  1. 遍歷collection中的元素有四種方式,最基本的辦法就是for循環,其次是NSEnumerator遍歷法,還有快速遍歷法(for in),以及塊枚舉法。塊枚舉是最新,最早進的方式。
  2. 塊枚舉法是經過GCD來併發執行遍歷操做
  3. 若提早知道待遍歷的collection含有何種對象,則應修改塊簽名,指出對象的具體類型。

第四十九條:對自定義其內存管理語義的collecion使用無縫橋接

經過無縫橋接技術,能夠在定義於Foundation框架中的類和CoreFoundation框架中的C語言數據結構之間來回轉換。 下面代碼展現了簡單的無縫橋接:

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與之相反。這裏要注意用完了數組要本身釋放,使用CFRelease(aCFArray)前面有提到過的。

第五十條:構建緩存時選用NSCache而非NSDictionary

在構建緩存時應該儘可能選用NSCache而非NSDictionary,NSCache會在系統資源將要耗盡時自動刪減緩存,而使用NSDictionary只能經過系統低內存警告方法去手動處理。此外NSCache還會看狀況刪減最久未使用的對象,並且是線程安全的。

第五十一條:精簡initialize與load的實現代碼

  1. load與initialize 方法都應該實現的精簡一點,這樣有助於保持應用程序的響應能力,也能夠減小引入依賴環的概率
  2. 沒法在編譯器設定的全局常量,能夠放在initialize方法裏面初始化。 另外沒搞清楚load 與 initialize的能夠看這裏, 我以前有出過一道有點腦殘有點繞的題(別拍磚,😆),能夠點擊這裏查看

第五十二條:別忘了NSTimer會保留其目標對象

在iOS開發中常常會用到定時器:NSTimer,因爲NSTimer會生成指向其使用者的引用,而其使用者若是也引用了NSTimer,那就造成了該死的循環引用,好比下面這個例子:

#import <Foundation/Foundation.h>

@interface EOCClass : NSObject
- (void)startPolling;
- (void)stopPolling;
@end
@implementation EOCClass {
     NSTimer *_pollTimer;
}
- (id)init {
     return [super init];
}
- (void)dealloc {
    [_pollTimer invalidate];
}
- (void)stopPolling {

    [_pollTimer invalidate];
    _pollTimer = nil;
}
- (void)startPolling {
   _pollTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
                                                 target:self
                                               selector:@selector(p_doPoll)
                                               userInfo:nil
                                                repeats:YES];
}
- (void)p_doPoll {
    // Poll the resource
}
@end
複製代碼

若是建立了本類的實例,並調用其startPolling方法開始定時器,因爲目標對象是self,因此要保留此實例,由於定時器是用成員變量存放的,因此self也保留了計時器,因此此時存在保留環。此時要麼調用stopPolling,要麼令系統將此實例回收,只有這樣才能打破保留環。 這是一個很常見的內存泄漏,那麼怎麼解決呢?這個問題能夠經過block來解決。能夠添加這樣的一個分類:

#import <Foundation/Foundation.h>
//.h
@interface NSTimer (EOCBlocksSupport)

+ (NSTimer*)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                         block:(void(^)())block
                                         repeats:(BOOL)repeats;
@end
//.m
@implementation NSTimer (EOCBlocksSupport)

+ (NSTimer*)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                         block:(void(^)())block
                                        repeats:(BOOL)repeats
{
             return [self scheduledTimerWithTimeInterval:interval
                                                  target:self
                                                selector:@selector(eoc_blockInvoke:)
                                                userInfo:[block copy]
                                                 repeats:repeats];

}
+ (void)eoc_blockInvoke:(NSTimer*)timer {
     void (^block)() = timer.userInfo;
         if (block) {
             block();
        }
}
@end
複製代碼

EOF : 因爲我的能力有限,不免有一些遺漏或者錯誤,請各位看官不吝賜教!謝謝!同時若是有任何問題也能夠在下方留言,歡迎一塊兒交流進步~最後感謝做者Matt Galloway以及譯者!更多細節仍是請翻閱圖書,能夠點擊這裏下載PDF版,原版英文版PDF我也有存~本文已經同步到我的博客

相關文章
相關標籤/搜索