1. 程式人生 > >編寫OC高質量的代碼的有效方法

編寫OC高質量的代碼的有效方法

有意義的字符串 write ssm 監聽 long 靜態常量 bsp 程序庫 del

2. 在類的頭文件中盡量少引入其他頭文件

  • 除非確有必要,否則不要引入頭文件。一般來說,應在某個類的頭文件中使用向前聲明來提及別的類(使用@class),並在實現文件中引入那些類的頭文件,這樣做可以盡量降低類之間的耦合。
  • 如果要聲明某個類遵循某個協議,應該把這個協議放到分類中,或者把協議單獨放在一個頭文件中,然後將其引入。

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

  下面是兩種方式的對比:

1 2 3 4 5 6 7 8 9 10 11 12 13 // 使用字面量語法的例子 NSArray *array1 = @[@"1",,@"2"]; NSNumber *number1 =
@1; NSDictionary *dictionary1 = @{@"key":@"value"}; // 使用與之對應的方法 NSArray *array2 = [NSArray arrayWithObjects:@"1",@"2",nil]; NSNumber *number2 = [NSNumber numberWithInt:2]; NSDictionary *dictionary2 = [NSDictionary dictionaryWithWithObjectsAndKeys:@"value":@"key"];
  • 使用字面量語法來創建字符串、數值、數組、字典。與常規方法相比,更加簡明扼要
  • 應該通過取下標操作來訪問數組下標或字典中的鍵所對應的元素
  • 使用字面量語法創建數組或字典時,若值中有nil,則會拋出異常,因此,需確保值裏面不含nil

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

定義一個常量的方法:

1 2 3 4 5 // 第一種:預處理指令 #define ANIMATION_DURATION 0.3 // 第二種:定義靜態常量 static const NSTimeInterval kAnimationDuration = 0.3

我們一般推薦使用第二種,這個方式定義的常量包含類型信息,有助於代碼閱讀。

註意:常量命名法是:若常量局限於“編譯單元”(也就是實現文件,.m文件)之內,則在前面加字母k;若常量在類之外可見,則通常以類名為前綴。

如我們需要對外公布某個常量,我們可以寫成下面的代碼:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // Test.h #import <Foundation/Foundation.h> extern NSString *const TestDidChangeNotification; @interface Test : NSObject @end // Test.m #import "Test.h" NSString *const TestDidChangeNotification = @"TestDidChangeNotification"; @implementation Test
  • 不要用預處理指令定義常量。這樣定義出來的常量不含類型信息,編譯器只是會在編譯前根據此執行查找和替換。即使有人重新定義了常量值,編譯器也不會有警告,這將導致應用程序中的常量值不一致
  • 在.m文件中使用 static const 來定義“編譯單元內可見常量”,無需加類名前綴,加k
  • 在頭文件中使用 extern 來聲明全局常量,並在相關實現文件中定義其值,這種常量要加類名前綴。

5. 用枚舉來表示狀態、選項、狀態碼

  • 使用枚舉來表示狀態機的狀態、傳遞給方法的選項以及狀態碼等值,給這些值起個易懂的名字
  • 用NS_ENUM 與 NS_OPTIONS 宏來定義枚舉類型,並指明其底層數據類型。
  • 在處理枚舉類型的switch語句中不要事先default分支,這樣的話,加入新枚舉之後,編譯器就會提示開發者:switch語句並未處理所有枚舉

6. 理解“屬性”這一概念

  • 使用@property語法來定義對象中所封裝的數據
  • 通過“特質”屬性關鍵字來指定存儲數據所需的正確語義
  • 在設置屬性所對應的實例變量時,一定要遵從該屬性所聲明的語義。

7. 在對象內部盡量直接訪問實例變量

比如,Person類有個name屬性,我們在這個類的內部想獲取這個name屬性的數據的時候,一種是通過 self.name,一種是 _name.

這兩種的區別:

  • 直接訪問實例變量的速度比較快,編譯器所生成的代碼會直接訪問保存對象實例變量的那塊內存
  • 直接訪問實例變量,不會調用其“設置方法”,這就繞過了為相關屬性所定義的“內存管理語義”,比如,在ARC下直接訪問一個聲明為copy的屬性,那麽並不會拷貝該屬性,只會保留新值,釋放舊值
  • 如果直接訪問實例變量,那麽不會觸發“KVO”,這樣做是否會產生問題,取決於具體的對象行為。
  • 通過屬性來訪問有助於排查與之相關的錯誤,因為可以給“獲取方法”或“設置方法”中新增“斷點”,監控該屬性的調用者及其訪問時機。

註意點:

  • 在對象內部讀取數據時,應該直接通過實例變量來讀,而寫入數據時,則應通過屬性來寫
  • 在初始化方法及dealloc方法中,總是應該直接通過實例變量來讀寫數據
  • 有時候會使用惰性初始化技術配置某份數據,這種情況下,需要通過屬性來讀取數據

8. 理解“對象等同性”這一概念

  • 若想檢測對象的等同性,請提供“isEqual:”與hash方法
  • 相同的對象必須具有相同的哈希碼,但是兩個哈希碼相同的對象卻未必相同
  • 不要盲目的逐個檢測每條屬性,而是根據具體需求來指定方案

9. “以類族模式”隱藏實現細節

“類族”是一種很有種的模式,可以隱藏“抽象基類”背後的實現細節。OC的系統框架中普遍使用此模式,比如有一個處理雇員的類,每個雇員都有“名字”和“薪水”這兩個屬性,管理者可以命令其執行日常工作,但是各種雇員的工作內容卻不同,經理在帶領雇員做項目時,無需關系每個人如何完成其具體工作,僅需指示其開工就行。我們重構多個子類,把每個人完成具體工作的方法,在子類實現。

首先定義一個抽象基類:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 typedef NS_ENUM(NSUInteger, EOCEmployeeType){ EOCEmployeeTypeDeveloper, EOCEmployeeTypeDesigner, EOCEmployeeTypeFinance } @interface EOCEmployee : NSObject @property (copy, nonatomic) NSString *name; @property (assign, nonatomic) NSInteger salary; // 創建一個雇員對象 +(EOCEmployee*)employeeWithType:(EOCEmployeeType)type; // 讓雇員工作 - (void)doADaysWork; @implementation EOCEmployee + (EOCEmployee *)employeeWithType:(EOCEmployeeType)type{ switch (type){ case EOCEmployeeTypeDeveloper: return [EOCEmployeeTypeDeveloper new]; break; case EOCEmployeeTypeDeveloper: return [EOCEmployeeTypeDesigner new]; break; case EOCEmployeeTypeDeveloper: return [EOCEmployeeTypeFinance new]; break; } } - (void)doADayWork{ // 子類去實現 } @end

然後,每個“實體子類”都從基類繼承而來,例如:

1 2 3 4 5 6 7 8 9 10 11 @interface EOCEmployeeDeveloper : EOCEmployee @end @implementation EOCEmployeeDeveloper - (void)doADaysWork{ [self wirteCode]; } @end

在本例中,基類實現了一個“類方法”,該方法根據待創建的雇員類別分配好對應的雇員類實例,這種“工廠模式”是創建類族的辦法之一。

如果對象所屬的類位於某個類族中,你可能覺得自己創建了某個類的實例,然而實際上創建的卻是其子類的實例。

OC中的NSNumber、NSArray等都是類族。

  • 類族模式可以把實現細節隱藏在一套簡單的公共接口後面。
  • 系統框架中經常使用類族
  • 從類族的公共抽象基類中集成子類時要當心,若有開發文檔,應先閱讀。

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

有時候需要在對象中存放相關信息,這時候我們通常都會從對象所屬類中繼承一個子類,然後改用這個子類對象,但是有時候類的實例可能是由某種機制所創建的,而開發者無法令這種機制創建出自己所寫的子類實例。OC中有一項強大的特性可以解決,就是“關聯對象”。

基於runtime來實現,此處就不多說。

  • 可以通過“關聯對象”機制來把兩個對象連起來。
  • 定義關聯對象時可指定內存管理語義,用以模仿定義屬性時所采用的“擁有關系”與“非用有關系”
  • 只有在其他做法不可行時才應該選用關聯對象,這種做法通常會引入難於查找的bug

11. 理解objc_msgSend的作用

在對象上調用方法是OC中經常使用的功能。專業術語叫做:“傳遞消息”。消息有“名稱”或“選擇子”,可以接受參數,而且可能還有返回值。

C語言使用“靜態綁定”,在編譯期就能決定運行時所應調用的函數。

OC中使用“動態綁定”,對象接收到消息之後,究竟該調用哪個方法則完全於運行期決定,甚至可以在程序運行時改變。

這裏就不多解釋objc_msgSend的使用,如有需要可以看runtime的使用。

objc_msgSend 函數會根據接收者和選擇子的類型來調用適當的方法,為了完成此操作,該方法需要在接收者所屬的類中找到其“方法列表”,如果能找到與選擇子名稱相符的方法,就跳至其實現代碼,如果找不到,那就沿著繼承體系向上查找,如果最終沒找到,則執行“消息轉發”操作。每個類都會有一塊緩存,用來緩存方法,若是稍後還向該類發送與選擇子相同的消息,那麽執行起來就會很快了。

  • 消息由接收者,選擇子及參數構成。給某對象“發送消息”,也就相當於在該對象上“調用方法”
  • 發給某對象的全部消息都要由“動態消息派發系統”來處理,該系統會查出對應的方法,並執行其代碼。

12. 理解消息轉發機制

當對象接收到無法解讀的消息後,就會啟動“消息轉發”機制,程序員可經由此過程告訴對象應該如何處理未知消息。

如果在控制臺中看到 unrecognized selector sent to instance 0x87 就說明你曾向某個對象發送過一條其無法解讀的消息,從而啟動了消息轉發機制,然後以程序崩潰而告終。

消息轉發分為兩個階段:

  1. 征詢接收者,所屬的類,看其是否能動態添加方法,以處理當前這個“未知的選擇子(unknown selector)”,這叫做“動態方法解析”
  2. 第二階段,涉及“完整的消息轉發機制”,如果運行期系統已經把第一階段執行完了,那麽接收者自己就無法再以動態新增方法的手段來響應包含該選擇子的消息了。此時,運行期系統會請求接收者以其他手段來處理與消息相關的方法調用。這又分為兩小步:
    1. 首先,看接收者看看有沒有其他對象能否處理這條消息
    2. 如果有,則運行期系統會把消息轉給那個對象,於是轉發郭恒結束,如果沒有“備用的接收者”,則啟動完整的消息轉發機制,運行期系統會把與消息有關的全部細節都封裝到NSInvocation對象中,再給接收者最後一次機會,令其設法解決當前還未處理的這條消息。

動態方法解析:

對象在收到無法解讀的消息後,首先將調用其所屬類的下列類方法:

1 // 如果該類調用了一個沒有實現的實例方法,會調用此方法<br>+ (BOOL)resolveInstanceMethod:(SEL)selector<br>// 如果該類調用了一個沒有實現的類方法,會調用此方法<br>+ (BOOL)resolveClassMethod;

 該方法的參數就是那個未知的選擇子,其返回值為Boolean類型,表示這個類是否能新增一個實例方法用以處理此選擇子。在繼續往下執行轉發機制之前,我們可以使用runtime動態的增加這個方法。

使用這種辦法的前提是:相關方法的實現代碼已經寫好,只等著運行的時候動態插在類裏面就可以了。

備用接收者:

當前接收者還有第二次機會能處理未知的選擇子,在這一步中,運行期系統會問它:能不能把這條消息轉給其他接收者來處理:

1 2 // 方法參數代表未知的選擇子,若當前接收者能夠找到備援對象,則將其返回,如果找不到就返回nil。 - (id)forwardingTargetForSelector:(SEL)selector;

 我們可以用“組合”來模擬出“多重繼承”的某些特性,在一個對象內部,可能還有其它一系列對象,該對象可經由此方法將能夠處理某選擇子的相關內部對象返回,這樣的話,在外界看來,好像是由該對象親自處理的。

完整的消息轉發:

如果轉發已經來到這一步的話,那麽唯一能做的就是啟用完整的消息轉發機制了,系統會創建NSInvocation 對象,把與尚未處理的那條消息有關的全部細節都封裝於其中,此對象包含選擇子、目標(target)及參數,在觸發NSInvocation對象時,“消息派發系統”將親自出馬,把消息指派給目標對象。

此步驟會調用下列方法來轉發消息:

1 2 // 該方法可以實現的很簡單,只需要改變調用目標,是消息在新目標上得以調用即可,然而這樣實現出來的方法與“備援接收者”方案所實現的方法等效,所以很少有人采用這麽簡單的實現方法,比較有用的實現方式為:在觸發消息前,先以某種方式改變消息內容,比如追加另外一個參數,或是改換選擇子等等。 - (void)forwardInvocation:(NSInvocation *)invocation;

 實現此方法時,若發現某調用操作不應由本類處理,則需要調用超類的同名方法。這樣的話,繼承體系中的每個類都有機會處理此調用請求,直到NSObject,如果最後調用了NSObject類的方法,那麽該方法還會繼續調用“doesNotRecognizeSelector”,以拋出異常,此異常表明選擇子最終未能得到處理。

消息轉發全流程:

技術分享圖片

技術分享圖片

接收者在每一步中均有機會處理消息,步驟越往後,處理消息的代價就越大,最好能在第一步處理完,這樣的話,運行期系統就可以將此方法緩存起來了,如果這個類的實例稍後還收到同名選擇子,那麽根本無需啟動消息轉發流程。如果想在第三步裏把消息轉給備援的接收者,那還不如把轉發操作提前到第二步。因為第三步只是修改了調用目標,這項改動放在第二部執行會更為簡單,不然的話,還得創建並處理完整的NSInvocation。

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

http://www.cocoachina.com/ios/20150604/12013.html 相關的例子

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

主要就是runtime的方法交換,runtime具體可見OC類目中關於runtime的介紹。

我們在這裏簡單的分析下:

類的方法列表會把選擇子的名稱映射到相關的方法實現直上,使得“動態消息派發系統”能夠據此找到應該調用的方法,這些方法均以函數指針的形式來表示,這種指針叫做IMP,其原型如下:

id (*IMP)(id,SEL,...)

比如,NSString 類可以相應lowercaseString、uppercaseString、capitalizedString等選擇子。這張映射表中的每個選擇子都映射到了不同的IMP之上:

技術分享圖片

OC運行期系統提供的幾個方法都能夠用來操作這張表,開發者可以向其中新增選擇子,也可以改變某選擇子所對應的方法實現,還可以交換兩個選擇子所映射到的指針,比如我們交換 lowercaseString 和 uppercaseString 的方法實現,類的方法表就會變成以下這個樣子:

技術分享圖片

在新的映射表中,我們可以看到交換了lowercaseString 和 uppercaseString 的方法實現,並且多了一個名為newSelector的選擇子,上述修改均無需編寫子類,只要修改了“方法表”的布局,就會反映到程序中所有的NSString實例之上。

通過此方案,開發者可以為那些“完全不知道其具體實現”的黑盒方法增加日誌記錄功能,這有助於程序調試。

  • 在運行期,可以向類中新增或替換選擇子所對應的方法實現。
  • 使用另一份實現來替換原有的方法實現,這道工序叫做“方法調配”,也就是方法交換,開發者常用此技術向原有實現中添加新功能。
  • 一般來說,只有調試程序的時候才需要在運行期修改方法實現,這種做法不宜濫用。

14. 理解“類對象”的用意

對象類型並非在編譯期就綁定好了,而是要在運行期查找。而且,還有個特殊的類叫做id,它能指代任意的OC對象類型,一般情況下,應該指明消息接收者的具體類型,這樣的話,如果向其發送了無法解讀的消息,那麽編譯器就會產生警告信息,而類型為id的對象則不然,編譯器嘉定它能夠響應所有的消息。

“在運行期檢視對象類型”,這個操作也叫做“類型信息查詢”(內省),這個強大而有用的特性內置於Foundation框架的NSObject協議裏,凡是由公共根類(common root class)繼承而來的對象都要遵從此協議。在程序中不要直接比較對象所屬的類,明智的做法是調用“類型信息查詢方法”。

在此之前,我們看下OC對象的本質是什麽?

每個OC對象實例都是指向某塊內存數據的指針,所以在聲明變量時,類型後面要跟一個“*”字符,如下:

1 2 // pointerVariable可以理解成存放內存地址的變量,而NSString 自身的數據就存儲於那個地址中,因此可以說,該變量”指向“NSString 實例。所有OC對象都是如此, NSString *pointerVariable = @"Some string";

 描述OC對象所用的數據結構定義在運行期程序庫的頭文件裏,id類型本身也定義在這裏:

1 2 3 typedef struct objc_object{ Class isa; }*id;

 每個對象,結構體的首個成員是Class類的變量。該變量定義了對象所屬的類,通常稱為“is a”指針,例如,剛才的例子中所有的對象“是一個”(is a)NSString,所以其“is a”指針就指向NSString。Class對象也定義在運行期程序庫的頭文件中:

1 2 3 4 5 6 7 8 9 10 11 12 13 typedef stuct objc_class *Class; struct objc_class{ Class isa; Class super_class; const char *name; long version; long info; long instance_size; struct objc_ivar_list *ivars; struct objc_method_list *methodList; struct objc_cache *cache; struct objc_protocol_list *protocols; }

此結構體存放類的“元數據”,例如類的實例實現了幾個方法,具備多少個實例變量等信息。此結構體的首個變量也是isa指針,這說明Class本身亦為OC對象。結構體裏還有個變量為super_class,它定義了本類的超類。類對象所屬的類型(也就是isa指針所指向的類型),是另外一個類,叫做“元類”,用來表述類對象本身所具備的元數據。“類方法”就定義於此處,因為這些方法可以理解成類對象的實例方法。每個類僅有一個“類對象”,而每個“類對象”僅有一個與之相關的“元類”。

super_class 指針確立了繼承關系,而isa指針描述了實例所屬的類。

  • 每個實例都有一個指向Class對象的指針,用以表明其類型,而這些Class對象則構成了類的繼承體系。
  • 如果對象類型無法再編譯期確定,那麽就應該使用類型信息查詢方法來彈指
  • 盡量使用類型信息查詢方法來確定對象類型,而不要直接比較類對象,因為某些對象可能實現了消息轉發功能。

15. 用前綴避免命名空間沖突

應該為所有的名稱都加上適當的前綴,比如,你所在的公司焦作Effective Widgets,那麽就可以在公共部分代碼中使用EWS做前綴,如果有些代碼只用於Effective Browser的瀏覽器項目中,可以使用EWB作前綴。

前綴最好是三個字母的,因為Apple宣稱其保留使用所有“兩字母前綴”。

  • 選擇與你的公司,應用程序或二者皆有關聯之名稱作為類名的前綴,並在所有代碼中使用這一前綴
  • 若自己所開發的程序庫中用到了第三方庫,則應為其中的名稱加上前綴。

16. 提供“全能初始化方法”

UITableViewCell,初始化該類對象時,需要指明其樣式及標示符,標示符能夠區分不同類型的單元格,由於這種對象的創建成本較高,所以繪制表格時可依照標示符來復用,以提升程序效率,我們把這種可為對象提供必要信息以便其能完成工作的初始化方法叫做“全能初始化方法”。

1 2 3 4 5 // 比如創建一個NSDate - (id)init; - (id)initWithString:(NSString *)string; - (id)initWithTimeIntervalSinceNow:(NSTimeInterval)seconds; - (id)initWIthTimeIntervalSinceRefrenceDate:(NSTimeInterval)seconds;

第四個方法是全能初始化方法,也就是說其余的初始化方法都要調用它,當底層數據存儲機制改變時,只需修改此方法的代碼。

  • 在類中提供一個全能初始化方法,並在文檔裏指明。其它初始化方法均應調用此方法
  • 若全能初始化方法與超類不同,則需要復寫超類中的對應方法。
  • 如果超類的初始化方法不適用子類,那麽應該復寫這個超類方法,並在其中拋出異常。

17. 實現description方法

調試程序的時候,經常需要打印並查看對象信息,我們可以重寫該對象的description方法,如下:

技術分享圖片

  • 實現description方法返回一個有意義的字符串,用以描述該實例
  • 若想在調試時打印出更詳盡的對象描述信息,則應實現debugDescription方法

18. 盡量使用不可變對象

設計類的時候,應充分運用屬性來封裝數據,盡量把對外公布出來的屬性設為只讀,而且只在確有必要時才將屬性對外公布。

  • 盡量創建不可變的對象
  • 若某屬性僅可於對象內部修改,則在.m文件中,則將其由readonly變成readwrite屬性。
  • 不要把可變的collection作為屬性公開,而應提供相關方法,以此修改對象中的collection

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

給方法命名時註意事項:

  • 如果方法的返回值是新創建的,那麽方法名的某個詞應該是返回值的類型,除非還有修飾語,如:localizedString。屬性的存取方法不遵循這種命名方式。
  • 應該把表示參數類型的名詞放在參數前面。
  • 如果方法要在當前對象上執行操作,那麽應該包含動詞。
  • 不要使用str這種簡稱,使用全程。
  • Boolean屬性應加is前綴。如果某方法返回非屬性的Boolean值,那麽應該根據其功能,選用has或is當前綴。
  • 將get這個前綴留給那些借由”輸出參數“來保存返回值的方法。

總結:

  • 起名時應遵從標準的OC命名規範,這樣創建出來的接口更容易為開發者所理解。
  • 方法名要言簡意賅
  • 方法名不要使用縮略後的類型名稱
  • 給方法起名時的第一要務就是確保其風格與你自己的代碼或所要繼承的框架相符。

20. 為私有方法名加前綴

一個類所做的事情通常都要比從外面看到的更多,編寫類的實現代碼時,經常要寫一些在內部使用的方法。應該為這種方法的名稱加上某些前綴,這有助於調試,因為據此很容易就能把公共方法和私有方法區別開。

具體使用何種前綴,可根據個人喜好來定,其中最好包含下劃線和字母p,比如p_method。不要使用 _method,因為Apple公司喜歡單用一個下劃線做私有方法的前綴,可能會引起沖突。

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

21. 理解OC錯誤模型

  • 只有發生了可使整個應用程序崩潰的嚴重錯誤時,才使用異常。
  • 在錯誤不嚴重的情況下,使用NSError

22. 理解NSCopying協議

  • 若想讓自己所寫的對象具有拷貝功能,則需要實現NSCopying協議
  • 如果自定義的對象分為可變和不可變,那麽就要同時實現NSCopying和NSMutableCopying協議
  • 復制對象時需決定采用淺拷貝還是深拷貝,一般情況下執行淺拷貝

23. 通過委托與數據源協議進行對象間通信

委托模式:定義一套接口,某對象若想接受另一個對象的委托,則需要實現這個接口,以便成為其"委托對象",而這”另一個對象“則可以給其委托對象回傳一些信息,也可以在發生相關事件時通知委托對象。

  • 委托模式為對象提供了一套接口,使其可由此將相關事件告知其它對象
  • 將委托對象應該支持的接口定義成協議,在協議中把可能需要處理的事件定義成方法
  • 當某對象需要從另外一個對象中獲取數據時,可以使用委托模式,比如 tableView的dataSource
  • 如果有必要,可實現含有位段的結構體,將委托對象是否能響應相關協議方法這一信息緩存下來,比如,聲明一個屬性,記錄是否實現了某個方法。

24. 將類的實現代碼分散到便於管理的數個分類之中

  • 使用分類機制把類的實現代碼劃分成易於管理的小塊
  • 將應該視為”私有“的方法歸入名叫Private的分類中,隱藏實現細節。

25. 總是為第三方類的分類名稱加前綴

比如你想給系統類添加個方法,如果你沒有添加前綴的話,可能會覆蓋其方法。

  • 向第三方類中添加分類時,總應給其名稱加上你專用的前綴。
  • 給其中的方法名加上你專用的前綴。

26. 不要再分類中聲明屬性

  • 把封裝數據所用的全部屬性都定義在主接口裏
  • 在分類中,可以定義存取方法,但盡量不要定義屬性。

27. 使用 "class-continuation分類"隱藏實現細節

"class-continuation分類"和普通的分類不同,它必須定義在其所接續的那個累的實現文件裏。其重要之處在於,這是唯一能夠聲明實例變量的分類,而且此分類沒有特定的實現文件,其中的方法都應該定義在類的主實現文件裏。而且,和其它分類不同,它沒有名字,比如:

1 2 3 @interface Person () // Methods here @end
  • 通過“class-continuation分類”向類中新增實例變量
  • 如果某屬性在主接口中聲明為只讀,而類的內部又要用設置方法修改此屬性,那麽就在“class-continuation分類”中將其擴展為“可讀寫”
  • 把私有方法的原型聲明在“class-continuation分類”裏面
  • 若想讓類所遵循的協議不為人所知,則可於“class-continuation分類”中聲明。

28. 通過協議提供匿名對象

如下面的代碼:

1 @property (nonatomic, weak) id <WCEDelegate> delegate;

由於該屬性的類型id<EOCDelegate>,所以實際上任何類的對象都能充當這一屬性,對於具備此屬性的類來說,delegate就是”匿名的“。

  • 協議可在某種程度上提供匿名類型。具體的對象類型可以淡化成遵從某協議的id類型,協議裏規定了對象所應實現的方法
  • 使用匿名對象來隱藏類型名稱
  • 如過具體類型不重要,重要的是對象能夠響應(定義在協議裏的)特定方法,那麽可使用匿名對象來表示。

29. 理解引用計數

  • 引用計數機制通過可以遞增遞減的計數器來管理內存。對象創建好之後,其保留計數至少為1.若保留計數為正,則對象繼續存活,當保留計數將為0時,對象就銷毀了
  • 在對象聲明期中,其余對象通過引用來保留或釋放此對象,保留和釋放操作分別會遞增及遞減保留計數

30. ARC註意事項

  • 在ARC之後,程序員就無需擔心內存管理問題了
  • 不要手動管理
  • CoreFoundation對象不歸ARC管理,開發者必須適時調用CFRetain/CFRelease.

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

對象在經歷其生命周期後,最終會為系統所回收,這時就要執行dealloc方法,在每個對象的生命周期內,此方法僅執行一次,也就是當保留計數為0的時候,然而具體何時執行,則無法保證。

在dealloc方法中,一般都是移除觀測行為,註銷通知。

  • 在dealloc方法裏,應該做的事情就是釋放指向其它對象的引用,並取消原來訂閱的”kvo“或通知中心的等通知,不要做其它事情
  • 如果對象持有文件描述符等系統資源,那麽應該專門編寫一個方法來釋放此種資源。
  • 執行異步任務的方法不應再dealloc裏,只能在正常狀態執行的哪些方法也不應在dealloc裏調用,因為此時對象已處於正在回收的狀態了。

32. 以弱引用避免循環引用

如果兩個對象,相互引用,那麽這兩個對象都無法被釋放,產生內存泄露。

unsafe_unretained 和 weak的區別:

當指向某個實例的引用移除後,unsafe_unretained屬性仍指向那個已經回收的實例,而weak屬性則指向nil。weak比unsafe_unretained應用可以令代碼更安全。

  • 當某些引用設為weak,可避免出現循環引用
  • weak引用可以自動清空,也可以不自動清空。

33. 自動釋放池

  • 自動釋放池排布在棧中,對象收到autorelease消息後,系統將其放入最頂端的池裏
  • 合理運用自動釋放池,可降低應用程序的內存峰值
  • 使用@autoreleasepool

34. 為常用的block類型創建typedef

比如:

1 typedef void(^WCECompletionHander)(NSData *data);
  • 用typedef重新定義塊類型,可讓塊變量用起來更加簡單
  • 定義新類型時,應遵循命名規則

35. 使用block降低代碼分散程度

  • 在創建對象時,可以使用內聯的handler代碼塊將相關業務邏輯聲明
  • 比如網絡請求一般使用代碼塊來回調數據

編寫OC高質量的代碼的有效方法