《Effective Objective C 2.0 編寫高質量iOS與OS X代碼的52個有效方法》讀書筆記

第一章 熟悉Objective-C

第1條:瞭解OC起源
消息結構,運行時所執行代碼有運行時環境決定,而函數調用,則有編譯器決定。

第2條:類的頭文件中儘量少引用其他頭文件
向前聲明 @class 的好處:

1、是延遲引入,減少類的使用者所需的引入的頭文件數量

2、解決類之間的相互引用

第3條:多用字面量語法,少用與之等價的方法
NSNumber *number = [NSNumber numberWithInt:1];
NSNumber *number = @1;

優勢:
1、簡單、易讀、防Nil;特別是NSArray、NSDictionary生成時遇到nil會報錯,可以提前排查出問題
侷限:
2、生成的是不變量,如果需要可變量,需要mutbleCopy;
3、也緊緊侷限Foudation框架

第4條:多用類型常量,少用#define預處理指令
類型常量,用static const 聲明 = #define

預處理定義出來的常量不包含類型信息,編譯器只是會在編譯前根據此執行查找與替換。即使有人重新定義常量值,編譯器也不會警告,這樣會導致常量不一樣。

第5條:用枚舉表示狀態,選項,狀態碼

第二章 對象,消息,運行機制

第6條:熟悉“屬性”

第7條:在對象內部儘量直接訪問實例變量
在對象內部,讀實例變量時用下劃線,寫時用存取方法(屬性)來寫;

直接訪問

1、直接訪問速度更快,無需方法派發
2、初始化,dealloc用下劃線讀寫數據。
3、直接訪問,不會調用設置方法,copy屬性,不會拷貝屬性,而且保留新值釋放舊值
4、不能KVO
5、不便於調試

存取方法:

1、init中不要用存取方法,防止子類覆蓋
2、惰性初始化一定要用存取方法

第8條:理解“對象等同性”
== 判斷指針是否相等, isEqualTo判斷類型、屬性和hash值 (isEqual會根據類,進行方法分發,工廠方法),在複寫isEqual方法時,需要注意其他情況調用super

關於hash值:如果collection類型的屬性,直接寫死固定值,會造成該固定值的對應的value變多,而影響性能。如果通過整體求hash,也出現中間變量,存在性能損耗。可以多每個屬性求hash,在進行與或處理
注意:把對象放入collection之後,改變其內容會造成很嚴重的後果

第9條:以“類簇模式”隱藏實現細節
Cocoa裏面很多類族實現,這種工廠方式的實現,因此不能用[subA class] == [A class]的方式進行判斷,應該使用類型查詢方式isKindOfClass進行類型判斷。

第10條:在既有類中使用關聯對象存放自定義數據
objc_setAssociateObject(id object , const void *key , id value , objc_AssociationPolicy policy)

object:被關聯的對象;

key:唯一key;

value:關聯的對象;

police:狀態內存策略(copy,retain)

與Dictionary比較 設置關聯對象的key一般是“不透明指針”,所以用靜態全局變量作爲key;同時要指定內存管理語義,用於模仿擁有 和 非擁有關係

objc_getAssociateObject

objc_removeAssociateObject

不需要主動調用remove來移除關聯對象,一般調用objc_setAssociateObject來設置爲nil,objc_removeAssociateObject會將所有的關聯對象都移除。

第11條:理解objc_msgSend的作用
objc_msgSend(id self , SEL cmd , …) 參數可變的函數

消息由接受者、選擇子、參數組成,給對象發送消息,相當於對象調用方法
每個類都有一張函數調用表,key爲選擇子,value爲實際調用的函數值。尾調用優化技術,使跳轉更加簡單:直接跳轉,不需要調用堆棧,進行優化。
方法列表若是沒有找到,進入消息轉發,三次挽救機會:
1、+ (BOOL)resolveInstanceMethod:(SEL)sel
2、- (id)forwardingTargetForSelector:(SEL)aSelector
3、- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

  尾遞歸優化?

尾遞歸的判斷標準時:函數運行的最後一步是否調用自身,而不是是否在函數最後一行調用自身。

優化:不需要保存自身(外尾)的調用幀(即調用棧)。

第12條:理解消息轉發機制
1、動態方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel
2、備援接收者
- (id)forwardingTargetForSelector:(SEL)aSelector
3、完整的消息轉發
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

第13條:用“方法調配技術”調試“黑盒方法”
使用動態消息轉發,不用通過繼承子類,覆寫子類方法實現新功能。使用另一份實現替換原有的方法實現。

操作選擇器映射表,可以新增選擇子,添加新功能,無需編寫子類,只需修改“方法表”佈局。

交換方法實現:

void method_exchangeImplementations(Method m1,Method m2)

獲得方法實現:

Method class_getInstanceMethod(class aclass , SEl aSelector)

通過這樣的方式,就可以爲既有的方法增加新功能,一般爲黑盒方法增加日誌記錄功能,有助於調試。

第14條:理解“類對象”
isMemberOfClass 判斷對象是否爲某個對象特點定類的實例

isKindOfClass 判斷對象是否爲某類或其派生類的實例

儘量使用類型信息查詢方法來確定對象的類型,而不要直接比較類對象,因爲某些對象實現了消息轉發。

第三章 接口與API設計
第15條:用前綴避免命名空間衝突
在自己開發的程序中用到了第三方庫,則應爲其中的名稱加上前綴。(前三個字母都大寫)

第16條:提供“全能初始化方法”
designated initializer OR Initializer from NSCoding;子類與超類不同,子類需要覆蓋,超類需要在方法中寫Assert。

第17條:實現description方法 和 debugDescription方法

第18條:儘量使用不可變的對象
1、.h 中readonly, .m中readwrite,
但是在對象外面還可以通過KVC的方式(setValue forKey)進行更改(hack);更加brutal 是通過類型查詢信息找到對應實例變量在內存中的偏移量,從而進行設置
2、可變的collection,應該通過相關方法,修改可變對象

第19條:使用清晰而協調的命名方式
駝峯命名法

第20條:爲私有方法加前綴
不要用下劃線開頭,那是蘋果公司的。

第21條:理解OC錯誤模型
ARC不是異常安全的,拋出異常,未釋放的對象不能自動釋放。如果想“異常安全”,增加-fobc-arc-exception標誌;
嚴重錯誤,拋出NSException;不嚴重用nil,0、NSError

第22條:理解NSCoding協議
若想令自己的對象具有拷貝功能,需要實現NSCoding協議,實現copyWithZone:(NSZone *)zone方法。

如果對象分爲可變與不可變兩種版本,即要同時實現NSCoping和MutableCopying協議。

複製對象一般進行淺拷貝,深拷貝可單獨寫一個方法。深拷貝會將底層數據一起拷貝,包括實例變量。

第四章 協議與分類

第23條:通過委託和數據源協議進行對象間通信
1、把需要處理的事件方法定義成協議
2、對象從另外一個對象獲取數據時,定義成數據源協議
3、若有必要,可實現含有位段的結構體,將委託對象是否響應相關協議緩存其中,(直接在setDelegate方法中進行緩存

第24條:將類的實現分散至各個便於管理的分類中
1、劃分成不同的功能區

2、調試方便,因爲分類名會出現在類名後面;

3、私有的可以考慮private分類

第25條:爲第三方的分類名稱加前綴
1、如果二個分類提供的方法重名,後編譯分類方法會覆蓋前面分類方法,分類編譯順序與添加到工程中的順序有關;

2、如果方法名相同,分類會覆蓋蘋果自帶的方法

第26條:勿在分類屬性中聲明屬性
1、在“class-continuation分類”之外的其他分類中,可以定義存取方法,但儘量不要定義屬性,雖然技術上可行。

2、如果聲明,會出現warning,原因是分類中無法合成與聲明屬性相關的變量,所以需要在分類中實現存取方法,並且實現中聲明爲@dynamic,意思就運行時在提供。當然關聯對象也可以實現這種需求,但是仍然建議只在分類中提供方法,

3、把封裝的數據所用的全部屬性都定義在主接口裏。

第27條:使用“class-continuation分類”隱藏實現細節
爲什麼要有這種分類:因爲可以定義方法和實例變量,
隱藏實例方法和方法,也可以避免不必要頭文件的引入,特別是對於OC++而言,引入的C++頭文件。這樣.h中進行向前聲明,避免引入不必要的頭文件,
也可以將類遵循的協議放在class-continuation,但是向前聲明delegate卻會有警告,因爲引入.h文件,編譯器看不到協議的定義及包含的方法。
爲什麼可以定義方法和實例變量:因爲ABI機制,我們無須知道對象大小也可以使用。

第28條 通過協議提供匿名對象

1、如果具體類型不重要,只是能響應特定方法,那麼可使用匿名對象表示,聲明爲id類型,來隱藏類型名稱

第五章 內存管理

第29條:理解引用計數

第30條:ARC簡化引用計數

第31條 在dealloc中只釋放非OC對象引用並解除監聽
運行時會在適當的時候調用dealloc,不要主動調用dealloc,但是手動 需要最後調用 super dealloc

1、開銷較大或者系統內資源(比如文件描述符、套接字、大塊內存等)不在dealloc中進行,應該單獨提供方法進行釋放,還有一個原因是系統並不保證每個創建出來的對象dealloc都會執行,也可以考慮在Appdelegate中種植方法執行清理,防止內存泄露
2、在dealloc不建議調用其他函數,防止調用過程中對象已經銷燬。也不要調用屬性的存取方法,因爲有人會對其進行覆蓋,也可能處於KVO下。(這個存取方法待討論)
3、self.tableView.delegate 設置爲nil

第32條:編寫異常代碼時,留意內存管理
MRC時,可以將內存釋放寫在finnal裏面(提問:爲什麼不能寫在try 和 catch 裏面)【因爲在寫在try中,拋異常會跳到catch中,內存泄漏】,但是變量就必須放在塊的外面
ARC時:不會自動處理try catch的內存管理,因爲ARC不能調用release,所以需要很多樣板代碼,進行跟蹤清理對象,影響運行時性能。因爲ios認爲因爲異常而終止程序,內存管理也就沒有必要了。
1、通過-fobjc-arc-exceptions進行開啓安全異常處理,默認情況是關閉的。OC++模式是默認開啓的
2、建議通過NSError方式進行錯誤捕捉。

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

第34條:以“autoreleasepool”降低內存峯值

第35條:殭屍對象調試,內存管理問題
當對象已經釋放,但是還沒被覆蓋時,調用這塊內存會正常工作,但是存在很大風險。Cocoa:系統在回收對象時,可以不真的將其回收,而是把它轉化爲殭屍對象。NSZombieEnabled設置爲yes,或者在scheme中勾選。
殭屍類是從NSZombie模板複製出來的,並且可以保留原類名字,不採用繼承的方式,是因爲效率因素的考慮。

其實現原理是:
修改對象的isa指針,讓它指向殭屍類,使對象變成殭屍對象,殭屍類能影響所有的選擇子:打印相關消息,並且終止程序。
創建新類,並且轉化爲殭屍對象

第36條:不要使用retainCount

第六章 塊與大中樞派發

第37條:理解“塊”

第38條:爲塊創建typedef
return_type (^block_name)(parameters)
優點:
1、定義變量一樣定義block
2、重構時如果給Block多增加參數,那麼只需修改相應的塊簽名(typedef),其他引用block的地方也會自動報錯,避免遺漏

第39條:用handler塊降低代碼分散程度
1、使用delegate 會使代碼結構過於分散,可以直接只用回調塊,使塊和相關對象放在一起,避免通過delegate透傳數據
2、通過handler,增加隊列參數,決定放在哪個隊列上。
3、Error塊和Succes塊 放在一起,可以處理返回結果中數據異常的情況 VS Error 和 Success分開處理,更加清晰。

第40條:用塊引用其所屬性避免循環引用
1、設計API時,可以考慮在調用完complete的塊之後,將環中的某個對象設置爲nil,解除環,避免API調用者沒有處理保留環的問題。也可以通過weakify的方法。
2、API調用者可以通過weakify的方法,解除保留環的問題;

第41條:多用派發隊列,少用同步鎖

派發隊列更加單實現同步語義。
  GCD之前,有2種方法:
        1、同步塊 @synchrnoized(someObject)一般是對self創建鎖,但是其中會涉及與self無關的代碼,降低代碼效率。
        2、NSLock對象,通過[_lock lock] [lock unlock]加鎖,解鎖;NSRecuresiveLock 遞歸鎖
       同一個鎖的同步塊,順序執行

通過atomic 屬性同步,這是通過synchrnoized的方式實現的?
1、同步和異步派發結合可以實現加鎖機制一樣的同步問題,但是卻不阻塞異步派發的進程,但是仍然無法正確同步
2、使用同步隊列及柵欄塊可以令同步行爲更加高效。

3、異步派發,需要拷貝塊,因此異步派發不一定會比同步快,需要考慮拷貝塊與執行塊的時間

第42條:多用GCD,少用performSelector系列方法
1、會發生warning,導致內存泄漏,因爲編譯器不知道調用什麼選擇子,方法簽名、返回值,無法通過ARC對返回值進行管理。
2、選擇子太過侷限,返回類型(void或者id,不能是struct)和參數都有侷限。
3、如果把任務放在指定線程執行,用GCD和塊,畢竟塊可以捕獲外部變量。

第43條:掌握GCD和操作隊列的使用時機
NSOperation 好處:
1、取消操作 2、指定依賴關係 3、通過KVO監控NSoperation對象的屬性 4、指定操作的優先級 5、可以複用NSOperation對象
NSNotifationCenter 就是使用的操作隊列

第44條:使用dispatch group進行任務分組
1、dispatch_group_async 包含block,用於回調
2、dispatch_group_enter && dispatch_group_leave
dispatch_group_wait (阻塞)使用表示group可以阻塞的時間;dispatch_group_notify(不阻塞),使用group結束的回調。
dispatch_apply 用於重複執行的次數

第45條:dispatch_once 只執行一次、線程安全
1、之前通過@synchronized(self) 創建單例,比dispatch-once慢二倍
2、需要一個標記,標記聲明爲static 或者global,目的是標記都相同

第46條 不要使用dispatch_get_current_queue
dispatch_get_current_queue 已經廢棄,只做調試用

第七章 系統框架

第47條:熟悉系統框架

第48條:多用枚舉塊,少用for循

枚舉方式:

1.for循環
2.NSEnumerator 遍歷
3.快速遍歷、塊枚舉
4.塊枚舉,支持GCD來併發執行遍歷操作

typedef NS_OPTIONS(NSUInteger, NSEnumerationOptions) {
    NSEnumerationConcurrent = (1UL << 0),
    NSEnumerationReverse = (1UL << 1),
};

如果提前知道collection對象類型,應修改塊簽名,指出對象具體的類型

第49條:對自定義其內容管理語義的collection使用無縫橋接

第50條:構建緩存時選用NSCache,而非NSDictionary

第51條:精簡initialize與load實現代碼

第52條:NSTimer會保留其目標對象

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章