天天看點

Effective Objective C2 0 編寫高品質代碼的52個有效方法一、熟悉objective-C二、對象、消息、運作時三、接口與API設計四、協定與分類五、記憶體管理六、塊與大中樞派發七、系統架構

閑來無事的時候就把effective OC這本書的52個知識點就謄寫了下,沒事的時候可以看看。

一、熟悉objective-C

1.了解OC語言的起源

(1)OC為C語言添加了面向對象特性,是其超集。OC使用動态綁定的消息結構,也就是說,在運作時才會檢查對象類型。接收一條消息後,究竟應該執行何種代碼,由運作環境而非編譯器決定。 (2)了解C語言的核心概念有助于寫好OC程式。尤其要掌握記憶體模型與指針。

2.在類的頭檔案中盡量少引用其他的頭檔案

(1)除非确有必要,否則不要引入頭檔案。一般來說,應在某個類的頭檔案中使用向前聲明提及别的類,并在檔案中引入那些類的頭檔案。這樣做可以盡量降低類之間的耦合。 (2)有時無法使用向前聲明,比如要聲明某個類遵循一項協定。這種情況下,盡量把"該類遵循某協定"的這條聲明移至"class-continuation分類"中。如果不行的話,就把協定單獨放在一個頭檔案中,然後将其引入。

3.多用字面量文法,少用與之等價的方法

(1)應該使用字面量文法來建立字元串、數組、數值、字典。與建立此類對象的正常方法相比,這麼做更加簡明扼要。 (2)應該通過取下标操作來通路數組下标或字典中的鍵所對應的元素。 (3)用字面常量無法建立數組或字典時,若值中有nil,則會抛出異常。是以,務必確定值裡不含nil。

4.多用類型常量,少用#define預處理指令

(1)不要用預處理指令定義常量。這樣定義出來的常量不含類型資訊,編譯器隻是會在編譯前據此執行查找與替換操作。即使有人重新定義了常量值,編譯器也不會産生警告資訊,這将導緻應用程式中的常量不一緻。 (2)在實作檔案使用static const來定義"隻在編譯單元内可見的常量"(translation-unit-specific constant)。由于此類常量不在全局符号表中,是以無須為其添加名稱字首。 (3)在頭檔案中使用extern來聲明全局常量,并在相關實作檔案中定義其值。這種常量要出現在全局符号表中,是以其名稱應該加以區隔,通常用與之相關的類名作字首。

5.用枚舉值表示狀态、選項、狀态碼

(1)應該用枚舉值來表示狀态機的狀态、傳遞給方法的選項以及狀态碼等值,給這些值起一個易懂的名字。 (2)如果把傳遞給某個方法的選項表示為枚舉類型,而多個選項又可同時使用,那麼就将各選項值定義為2的幂,以便通過按位或操作将其組合起來。 (3)用NS_ENUM與NS_OPTIONS宏來定義枚舉類型,并指明其底層資料類型。這樣做可以確定枚舉是用開發者所選的底層資料類型實作出來的,而不會采用編譯器所選的類型。 (4)在處理枚舉類型的switch語句中不要實作default分支。這樣的話,加入新枚舉之後,編譯器就會提示開發者:switch語句未處理所有枚舉。

二、對象、消息、運作時

6.了解"屬性"這一概念

(1)可以用@property文法來定義對象中所封裝的資料。 (2)通過"特質"來指定存儲資料所需要的正确語義。 (3)在設定屬性所對應的執行個體變量時,一定要遵循該屬性所聲明的語義。 (4)開發iOS程式時應該使用nonatomic屬性,因為atomic屬性會嚴重影響性能。

7.在對象内部盡量直接通路執行個體變量

(1)在對象内部讀取資料時,應該直接通過執行個體變量來讀取,而寫入資料時,則應該通過屬性來寫。 (2)在初始化方法及dealloc方法中,總是應該直接通過執行個體變量來讀寫資料。 (3)有時會使用惰性初始化技術配置某份資料,這種情況下,需要通過屬性來讀取資料。

8.了解"對象等同性"這一概念

(1)若想檢測對象的等同性,請提供"is Equal:"與hash方法。 (2)相同的對象必須具有相同的哈希碼,但是兩個哈希碼相同的對象卻未必相同。 (3)不要盲目地逐個檢測每條屬性,而是應該依照具體需求來指定檢測方案。 (4)編寫hash方法時,應該使用計算速度快而且哈希碼碰撞幾率低的算法。

9.以"類族模式"隐藏實作細節

(1)類族模式可以把實作細節隐藏在一套簡單的公共接口後面。 (2)系統架構中經常使用類族。 (3)從類族的公共抽象基類中繼承子類時要當心,若有開發文檔,則應首先閱讀。

10.在既有類中使用關聯對象存放自定義資料

(1)可以通過"關聯對象"機制來把兩個對象連起來。 (2)定義關聯對象時可指定記憶體管理語義,用以模仿定義屬性時所采用的"擁有關系"與"非擁有關系"。 (3)隻有在其他做法不可行時才應選用關聯對象,因為這種做法通常會引入難于查找的bug。

11.了解objc_msgSend的作用

(1)消息由接收者、選擇子及參數構成。給某對象"發送消息"(invoke amessage)也就相當于在該對象上調用"調用方法"( call amethod)。 (2)發給某對象的全部消息都要由"動态消息派發系統"(dynamic message dispatch system)來處理,該系統會調查出對應的方法,并執行其代碼。

12.了解消息轉發機制

(1)若對象無法響應某個選擇子,則進入消息轉發流程。 (2)通過運作期的動态方法解析功能,我們可以在需要用到某個方法時再将其加入類中。 (3)對象可以把其他無法解讀的某些選擇子交給其他對象進行處理。 (4)經過上述兩步之後,如果還是沒辦法處理選擇子,那就啟動完整的消息轉發機制。

13.用"方法調配技術"調試"黑盒方法"

(1)在運作期,可以向類中新增或替換選擇子所對應的方法實作。 (2)使用另一份實作來替換原有的方法實作,這道工序叫做"方法調配",開發者常用此技術向原有實作中添加新功能。 (3)一般來說,隻有調試程式的時候才需要在運作期修改方法實作,這種做法不宜濫用。

14.了解"類對象"的用意

(1)每個執行個體都有一個指向Class對象的指針,用以表明其類型,而這些Class對象則構成了類的繼承體系。 (2)如果對象類型無法在編譯期确定,那麼就應該使用類型資訊查詢方法來探知。 (3)盡量使用類型資訊查詢方法确定對象類型,而不要直接比較類對象,因為某些對象可能實作了消息轉發功能。

三、接口與API設計

15.用字首避免命名空間沖突

(1)選擇與你的公司、應用程式或二者皆有關聯之名稱作為類名的字首,并在所有代碼中均适用這一字首。 (2)若自己所開發的程式庫中用到了第三方庫,則應為其中的名稱加上字首。

16.提供"全能初始化方法"

(1)在類中提供一個全能初始化方法,并于文檔裡指明。其他初始化方法均應調用此方法。 (2)若全能初始化方法與超類不同,則需要覆寫超類中對應方法。 (3)如果超類的初始化方法不适用于子類,難麼應該覆寫這個超類方法,并在其中抛出異常。

17.實作description方法

(1)實作description方法傳回一個有意義的字元串,用以描述該執行個體。 (2)若想在調試時列印出更詳盡的對象描述資訊,則應該實作debugDescription方法。

18.盡量使用不可變對象

(1)盡量建立不可變對象。 (2)若某屬性僅可于對象内部修改,則在"class-continuation分類"中将其由readonly屬性擴充為readwrite屬性。 (3)不要把可變的collection作為屬性公開,而應提供相關方法,以此修改對象中的可變collection。

19.使用清晰而協調的命名方式

(1)起名時應遵從OC命名規範,這樣建立出來的接口更容易為開發者所了解。 (2)方法名要言簡意赅,從左至右讀起來要像個日常用語中的句子才好。 (3)方法名裡不要使用縮略後的類型名稱。 (4)給方法起名時的第一要務就是確定其風格與你自己的代碼或所要內建的架構相符。

20.為私有方法名前加字首

(1)給私有方法的名稱前加上字首,這樣可以很容易地将其同公共方法區分開。 (2)不要單用一個下劃線做私有方法的字首,因為這種做法是預留給蘋果公司用的。

21.了解OC的錯誤模型

(1)隻有發生了可使整個應用程式崩潰的嚴重錯誤時,才應使用異常。 (2)在錯誤不那麼嚴重的情況下,可以指派"委托方法"(delegate method)來處理錯誤,也可以把錯誤資訊放在NSError對象裡,經由"輸出參數"傳回給調用者。

22.了解NSCopying協定

(1)若想令自己所寫的對象具有拷貝功能,則需要實作NSCopying協定。 (2)如果自定義的對象分為可變版本與不可變版本,那麼就要同時實作NSCopying與NSMutableCopyig協定。 (3)複制對象時需決定采用淺拷貝還是深拷貝,一般情況下應該盡量執行淺拷貝。 (4)如果你所寫的對象需要深拷貝,那麼考慮新增一個專門執行深拷貝的方法。

四、協定與分類

23.通過委托與資料源協定進行對象間通信

(1)委托模式為對象提供了一套接口,使其可由此将相關事件告知其他對象。 (2)将委托對象應該支援的接口定義成協定,在協定中把可能需要處理的事件定義成方法。 (3)當某對象需要從另一個對象中擷取資料時,可以使用委托模式。這種情況下,該模式亦稱"資料源協定"(data source protocal)。 (4)若有必要,可實作含有位段的結構體,将委托對象是否能響應相關協定方法這一資訊緩存至其中。

24.将類的實作代碼分散到便于管理的數個分類之中

(1)使用分類機制把類的實作代碼劃分成為易于管理的小塊。 (2)将應該視為"私有"的方法歸入名為Private的分類中,以隐藏實作細節。

25.總是為第三方類的分類名稱加字首

(1)向第三方類中添加分類時,總是給其名稱加上專用的字首。 (2)向第三方類中添加分類時,總給其中的方法名加上你專用的字首。

26.勿在分類中聲明屬性

(1)把封裝資料所用的全部屬性都定義在主接口裡。 (2)在"class-continuation分類"之外的其他分類中,可以定義存取方法,但盡量不要定義屬性。

27.使用"class-continuation分類"隐藏實作細節

(1)通過"class-continuation分類"向類中新增執行個體變量。 (2)如果某屬性在主接口中聲明為"隻讀",而類的内部又要設定方法修改此屬性,那麼就在"class-continuation分類"中将其擴充為"可讀寫"。 (3)把私有方法的原型聲明在"class-continuation分類"裡面。 (4)若想使類所遵循的協定不為人所知,則可于"class-continuation分類"中聲明。

28.通過協定提供的匿名對象

(1)協定可在某種程度上提供匿名類型。具體的對象類型可于淡化成遵從某協定的id類型,協定裡規定了對象所應該實作的方法。 (2)使用匿名對象來隐藏類型名稱(或類名)。 (3)如果具體類型不重要,重要的是對象能夠響應(定義在協定裡的)特定方法,那麼可使用匿名對象來表示。

五、記憶體管理

29.了解引用計數

(1)引用計數機制可以遞增遞減的計數器來管理記憶體。對象建立好之後,其保留計數至少為1.若保留計數為正,則對象繼續存活。 當保留計數降為0時,對象被銷毀。 (2)在對象生命周期中,其餘對象通過引用來保留或釋放此對象。保留與釋放操作分别會遞增及遞減保留計數。

30.以ARC簡化引用計數

(1)有ARC之後,程式員就無須擔心記憶體管理問題了。使用ARC來程式設計,可省去類中的許多“樣闆代碼”。 (2)ARC管理對象生命期的辦法基本上就是:在合适的地方插入“保留”及“釋放”操作。在ARC環境下,變量的記憶體管理語義可以通過修飾符指明,而原來則需要手工執行“保留”及“釋放”操作。 (3)由方法所傳回的對象,其記憶體管理語義總是通過方法名展現。ARC将此确定為開發者必須遵守的規則。 (4)ARC隻負責管理objective-C對象的記憶體,尤其要注意:CoreFoundation對象不歸ARC管理,開發者必須适時調用CFRetain/CFRelease。

31.在dealloc方法中釋放引用并解除監聽

(1)在dealloc方法裡,應該做的事情就是釋放指向其他對象的引用,并取消原來訂閱的“鍵值觀察”(KVO)或NSNotificationCenter等通知,不要做其他事情。 (2)如果對象持有檔案描述等系統資源,那麼應該專門編寫一個方法來釋放此種資源。這樣的類要和其使用者約定:用完資源後必須調用close方法。 (3)執行異步任務的方法不應在dealloc裡調用;隻能在正常狀态下執行的那些方法也不應在dealloc裡調用,因為此時對象已處于正在回收的狀态了。

32.編寫“異常安全代碼”時留意記憶體管理問題

(1)捕獲異常時,一定要注意将try塊内所創立的對象清理幹淨。 (2)在預設情況下,ARC不生成安全處理異常所需的清理代碼。開啟編譯器标志後,可生成這種代碼,不過會導緻應用程式變大,而且會降低運作效率。

33.以若引用避免保留環

(1)講某些引用設為weak,可避免出現“保留環”。 (2)weak引用可以自動清空,也可以不自動清空。自動清空(autoniling)是随着ARC而引入的新特性,由運作期系統來實作。在具備清空功能的弱引用上,可以随意讀取其資料,因為這種引用不會指向已經回收過的對象。

34.以“自動釋放池塊”降低記憶體峰值

(1)自動釋放池排布在棧中,對象收到autorelease消息後,系統将其放入最頂端的池裡。 (2)合理運用自動釋放池,可降低應用程式的記憶體峰值。 (3)@autoreleasepool這種新式的寫法能建立出更為輕便的自動釋放池。

35.用“僵屍對象”調試記憶體管理問題

(1)系統在回收對象時,可以将其不真的回收,而把他轉化為僵屍對象。通過環境變量NSZombieEnabled可開啟此功能。 (2)系統會修改對象的isa指針,令其指向特殊的僵屍類,進而使該對象變成為僵屍對象。僵屍類能夠響應所有的選擇子,響應方式為:列印一條包含消息内容及其接受者的消息,然後終止應用程式。

36.不用使用retainCount

(1)對象的保留計數看似有用,實則不然,因為任何時間點給定的“絕對保留計數”(absolute retain count)都無法反應對象生命周期的全貌。 (2)引入ARC之後,retainCount方法就真是廢止了,在ARC下調用此方法會導緻編譯器報錯。

六、塊與大中樞派發

37.了解“塊”這一概念

(1)塊是C、C++、Objective-C中的詞法閉包。 (2)塊可以接受參數,也可傳回值。 (3)塊可以配置設定在棧或堆上,也可以是全局的。配置設定在棧上的快可以拷貝到堆上,這樣的話,就和标準的Objective-C對象一緻了,具備引用計數了。

38.常用的塊類型建立typedef

(1)以typedef重新定義塊類型,可令塊變量用起來更加簡單。 (2)定義新類型時應遵從現有的命名習慣,勿使其名稱與别的類型相沖突。 (3)不妨為同一個塊簽名定義多個類型的别名。如果要重構的代碼使用了塊類型的某個别名,那麼隻需要修改相應的typedef中的塊簽名即可,無須改動其他的typedef。

39.用handler塊降低代碼分散程度

(1)在建立對象,可以使用内聯的handler塊将業務邏輯一并聲明。 (2)在多個執行個體需要監控時,如果采用委托模式,那麼經常需要需要根據傳入的對象來切換,而若改用handler塊來實作,則可直接将塊與相關對象放在一起。 (3)設計API時如果用到handler塊,那麼可以增加一個參數,使調用者可以通過此參數來決定應該把塊安排在哪個隊列上執行。

40.用塊引用所屬對象時不要出現保留環

(1)如果塊所捕獲的對象直接或間接的保留了塊本身,那麼就要當心保留環問題。 (2)一定要找個适當的時機接觸保留環,而不能把責任推給API的調用者。

41.多用派發隊列,少用同步鎖

(1)派發隊列可以用來表述同步語義(synchronization semantic),這種做法要比時候用@synchronized塊或NSLock對象更為簡單。 (2)将同步或異步結合起來,可實作與普通加鎖機制一樣的同步行為,而這麼做卻不會阻塞執行異步派發的線程。 (3)使用同步隊列或栅欄快,可以令同步行為更加高效。

42.多用GCD,少用performSelector系列方法

(1)performSelector系列方法在記憶體管理方面容易有疏失。它無法确定将要執行的選擇子的具體是什麼,因而ARC編譯器無法插入适當的記憶體管理方法。 (2)performSelector系列方法所能處理的選擇子太多局限了,選擇子的傳回值類型及發送給方法的參數個數都受到限制。 (3)如果想把任務方法哦另一個線程上執行,那麼最好不要用performSelector系列方法,而是應該把任務封裝到塊裡,然後調用大中樞派發機制的相關方法來實作。

43.掌握GCD和操作隊列的使用時機

(1)在解決多線程和任務管理問題時,派發隊列并非唯一方案。 (2)操作隊列提供了一套高層的Objective-C API,能實作純GCD的所具備的絕大部分的功能,而且還能完成一些更為複雜的操作,那些操作如果改為GCD實作,則需要另外編寫代碼。

44.通過Dispatch Group機制,根據系統資源狀況來執行任務

(1)一系列任務可歸入一個dispatch group中。開發者可以在這組任務執行完畢的時候獲得通知。 (2)通過dispatch group,可以在并發式派發隊列中同時執行多項任務。此時GCD會根據系統資源來排程這些并發執行的任務。開發者若要實作此功能,則會需要寫大量的代碼。

45.使用dispatch_once來執行隻需要運作一次的線程安全代碼

(1)經常需要編寫“隻需要執行一次的線程安全代碼”(thread-safe single-code excution)。通過GCD所提供的dispatch_once函數,就很容易實作次功能。 (2)标記應該聲明在static或是global作用域中,這樣的話,在隻需要執行一次的塊傳給dispatch_once函數時,傳進去的标記也是相同的。

46.不要使用dispatch_get_current_queue

(1)dispatch_get_current_queue函數的行為常常與開發者所預期的不同。此函數已經廢棄,隻做調試的時候使用。 (2)由于派發隊列是按層級來組織的,是以無法單用某個隊列對象來描述“目前隊列”這一概念。 (3)dispatch_get_current_queue函數常用于解決由不可重入的代碼引發的死鎖,然而能用此函數解決的問題,通常也能用改用"隊列特定資料"來解決。

七、系統架構

47.熟悉系統架構

(1)許多系統架構都可以直接使用。其中最重要的是Foundation與CoreFoundation,這兩個架構提供了建構應用程式所需的許多核心功能。 (2)很多常見功能都能用架構來做,例如音頻與食視訊處理、網絡通信、資料管理等。 (3)請記住:用純C寫的架構和與用objective-c寫成的架構是一樣的重要的,若想成為優秀的oc開發者,需要掌握C語言的核心。

48.多用塊枚舉,少用for循環

(1)周遊collection有四種方法。最基本的方式就是for循環,其次是NSEnumerator周遊法以及快速周遊法,最新、最先進的方式則是"塊枚舉法"。 (2)"塊枚舉法"本身就是通過GCD來并發執行周遊操作,無須另行編寫代碼。而采用其他的周遊方式則無法輕易實作這一點。 (3)若提前得知被周遊的collection是何種對象,則應該修改塊簽名,指出對象的具體類型。

49.對自定義其記憶體管理語義的collection使用無縫橋接

(1)使用無縫橋接技術,可以在Foundation架構中的objective-c對象與CoreFoundation架構中的C語言資料結構之間來回轉換。 (2)在CoreFoundation層面建立collection時,可以指定許多回調函數,此函數表示collection應如何處理元素。然後,可運用無縫橋接技術,将其轉換成具備特殊記憶體管理語義的objective-c的collection。

50.建構緩存的時候選用NSCache而非NSDictionary

(1)實作緩時應選用NSCache而非NSDictionary對象。因為NSCache可以提供優雅的自動删減功能,而且是"線程安全的",此外,它與字典不同,并不會拷貝鍵。 (2)可以給NSCache對象設定上限,用以限制緩存中的對象總個數以及"總成本",而這些尺度則定義了緩存删除其中對象的時機。但絕對不用這些尺度當成可靠的“硬限制”,他們進隊NSCache起一個引導作用。 (3)将NSPurgeableData與NSCache搭配使用,可實作自動清除資料的功能,也就是說,當NSPurgeableData對象所占記憶體為系統所丢棄時,該對象也會從從緩存中移除。 (4)如緩存使用得當,那麼應用程式的響應速度就能提高。隻有那種“重新計算很費勁”的資料,才能得放入緩存,比如那些需要從網絡擷取或磁盤讀取的資料。

51.精簡initialize和load的實作代碼

(1)在加載階段,如果實作了load方法,那麼系統就會調用它。分類裡也可以定義此方法,類的load方法要比分類中的先調用。與其他方法不同,load方法不會參與覆寫機制。 (2)首次使用某個類之前,系統會向其發送initialize消息。由于此方法遵從普通的覆寫規則,是以通常應該在裡面判斷目前要初始化的是哪個類。 (3)load和initialize方法都應該實作得精簡些,這有助于保持應用程式的響應能力,也能減少引入"依賴環"(interdependcy circle)的幾率。 (4)無法再編譯器設定的全局常量,可以在initialize方法裡進行初始化。

52.别忘了NSTimer會保留其目标對象

(1)NSTimer對象會保留其目标,知道計時器本身失效為止,調用invalidate方法可以令計時器失效,另外,一次性的計時器在觸發完任務之後也會失效。 (2)反複執行任務的計時器(repeating timer),很容易引入保留環,如果這種定時器的目标對象又保留了計時器本身,那肯定會導緻保留環。這種環裝保留關系,可能是直接發生的,也有可能是通過對象圖裡其他對象間接發生的。 (3)可以擴充NSTimer的功能,用"塊"來打破保留環。不過,除非NSTimer将來在公共接口裡提供此功能,否則必須建立分類,将相關實作代碼加入其中。