Objective-C
Objective-C
語言的起源使用 #import "ClassName.h"
能夠引入其餘文件的全部接口細節。git
使用 @class ClassName
「向前聲明」(forward declaring),只聲明有這個類,沒有具體細節,能夠解決上述問題。github
除非確實有必要,不然不要引入頭文件。通常來講,應在某個類的頭文件中使用向前聲明來說起別的類,並在實現文件中引入那些類的頭文件。這樣作能夠儘可能下降類之間的耦合(coupling)。objective-c
繼承 和 聽從協議 不能使用向前聲明。 有時沒法使用向前聲明,好比要聲明某個類遵循一項協議。這種狀況下,儘可能把「該類遵循某協議」的這條聲明移至「分類」中。若是不行的話,就把協議單獨放在一個頭文件中,而後將其引入。數組
向前聲明的做用:緩存
- 防止引入根本用不到的內容,減小頭文件細節引用。
- 解決兩個類相互引用的問題。
將引入頭文件的時機儘可能延後,只在確有須要時才引入,這樣能夠減小類的使用者所需引入頭文件的數量。函數
使用字面量語法(literal syntax)能夠縮減源代碼的長度,使其更爲易讀。性能
#define
預處理指令#define
定義的常量沒有類型信息,編譯器只會在編譯前據此執行查找與替換操做。即便有人從新定義了常量值,編譯器也不會產生警告信息,這將致使應用程序中的常量值不一致。優化
// .h 文件 @interface 類名: 父類名 ... @end // .m 文件 // 類內使用 static const 類型 常量名 = 常量值; @implementation 類名 ... @end
// .h 文件 // 類外可用聲明 extern 類名 const 常量名; @interface 類名: 父類名 ... @end // .m 文件 // 類外可用聲明 類名 const 常量名 = 常量; @implementation 類名 ... @end
常量名稱經常使用命名法是:編碼
k
。switch
語句中不要實現 default
分支,便於加入新枚舉後,編譯器報錯,知道須要修改的地方。==
操做符比較的是兩個指針自己,不是其所指的對象。NSObject
協議中聲明的 isEqual
方法判斷兩個對象的等同性。 isEqual
默認實現是:當且僅當其 「指針值」 徹底相等時,這兩個對象才相等。NSString
: isEqualToString:
NSArray
: isEqualToArray:
NSDIctionary
: isEqualToDictionary:
若比較的對象不是對應的類型,就會拋出異常,崩潰。類族模式:使用繼承,實現多種職能的子類,父類經過設定不一樣的類型來建立某種子類,執行其相應的職能。 做用:將實現細節隱藏在一套簡單的公共接口後面。 須要注意的是,建立的實例的真實類型是什麼,須要咱們知道atom
新增 Cocoa
中 NSArray
這樣的類族的子類,須要遵照如下幾條規則:
NSArray
自己只是包在其餘隱藏對象外面的殼,它僅僅定義了全部數組都需具有的一些接口。在對象中存放相關信息:
- 從對象所屬的類中繼承一個子類,而後修改這個子類對象。
- 經過「關聯對象」的特性,給某對象關聯許多其餘對象,這些對象經過「鍵」來區分。
以給定的鍵和存儲策略爲某對象設置關聯對象值
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key, id _Nullable value, objc_AssociationPolicy policy)
參數說明:
object
關聯的源對象。key
關聯的 key
。一般使用靜態全局變量作鍵。value
關聯 key
所對應的值。傳 nil
能夠清除現有的關聯。policy
關聯的存儲策略,也就是對應的內存管理語義,是一個枚舉值。objc_AssociationPolicy
枚舉值以下表: | 關聯類型 | 等效的 @property 屬性 | | --- | --- | OBJC_ASSOCIATION_ASSIGN | assign | OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic, retain | OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic, copy | OBJC_ASSOCIATION_RETAIN | retain | OBJC_ASSOCIATION_COPY | copy |根據給定的鍵從某對象中獲取對應的關聯對象值。
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
移除指定對象的所有關聯對象。
objc_removeAssociatedObjects(id _Nonnull object)
注:只有在其餘方法都行不通時才考慮使用它。如果濫用,則很快就會令代碼失控,使其難於調試。
objc_msgSend
的做用代碼:
id returnValue = [someObject messageName: paramter];
代碼說明:
someObject
接受者。messageName
選擇子。選擇子和參數合起來稱爲「消息」
底層C語言代碼實現:
id returnValue = objc_msgSend(someObject, @selector(messageName:), paramter);
原型代碼:
void objc_msgSend(id self, SEL cmd, ...)
這是個「參數個數可變函數」,能接受兩個或者兩個以上的參數。 參數說明:
self
接受者。cmd
選擇子(方法的名字)。- 後續參數爲消息中的那些參數,順序不變。
具體實現:
注:OC
方法調用須要不少步驟,較爲耗時。objc_msgSend
會將匹配結果緩存在「快速映射表」,每一個類都有這樣一塊緩存。雖然仍是不如「靜態綁定的函數調用操做」那麼迅速,可是也不會慢不少。
邊界狀況:
objc_msgSend_stret
待發送的消息要返回結構體,就交由此函數處理。objc_msgSend_fpret
待發送的消息要返回浮點數,就交由此函數處理。objc_msgSendSuper
要給超類發消息,就交由此函數處理。如:[super message:parameter]
。
Objective-C
對象的每一個方法均可以看作簡單的 C
函數,其原型以下:
<return_type> Class_selector(id self, SEL _cmd, ...)
這個原型和 objc_msgSend
函數很像,是爲了利用 「尾調用優化」 技術。令 「跳至方法實現」 這一操做跟簡單些。
使用範圍:某函數的最後一項操做僅僅是調用另外一個函數而不會將其返回值另做他用。 步驟:編譯器會生成調轉至另外一函數所需的指令碼,不會向調用堆棧中推人新的 「棧幀」。 不優化後果:
Objective-C
方法以前,都須要爲調用 objc_msgSend
函數準備 「棧幀」,能夠在 「棧蹤影」 中看到。消息轉發: 第一階段:動態方法解析: 徵詢接收者(所屬的類),看其是否能動態添加方法,以處理當前這個 「未知的選擇子」。 第二階段: 1. 備援的接收者: 請接收者看看有沒有其餘對象(備援的接收者)能處理這條消息。 2. 完整的消息轉發機制: 運行期系統會把與消息有關的所有細節都封裝到
NSInvocation
對象中,再給接收者最後一次機會,令其設法解決當前還未處理的這條消息。
是否能新增一個實例方法來處理選擇子,調用方法以下:
// 實例方法 + (BOOL)resolveInstanceMethod:(SEL)selector // 類方法 + (BOOL)resolveClassMethod:(SEL)selector
使用前提:相關方法的實現代碼已經寫好,只等運行的時候動態插入到類裏面。
動態添加方法函數以下:
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)參數說明:
cls
添加方法的類name
被添加方法的名字imp
函數指針,指向待添加的方法(C語言實現)。type
待添加方法的 「類型編碼」 。
是否有其餘對象處理這條消息,調用方法以下:
- (id)forwardingTargetForSelector:(SEL)selector
若找到備援對象,該方法返回備援對象,反之,返回 nil
。
注意:咱們沒法操做經由這一步所轉發的消息。
建立 NSInvocation
對象,此對象包含 選擇子 、目標 及 參數。
消息派發調用方法以下:
- (void)forwardInvocation:(NSInvocation *)invocation
若發現某調用操做不該由本類處理,則向上尋找,直至 NSObject
。若是最後調用了 NSObject
的方法,那麼該方法還會繼續調用 doesNotRecognizeSelector:
以拋出異常,代表選擇子最終未能獲得處理。
消息轉發全流程以下圖:
接收者在每一步中均有機會處理消息。步驟越日後,處理消息的代價就越大。
消息轉發代碼:https://github.com/AlonerOwl/Runtime/tree/master/Runtime/MessageSend
函數指針(IMP):id (*IMP)(id, SEL, ...)
方法表:函數指針所組成的一個集合。
操做類的方法表:
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)第12條已經說過了。
func method_exchangeImplementations(_ m1: Method, _ m2: Method)參數:兩個待交換的方法實現 獲取方法實現函數:
Method class_getInstanceMethod(Class cls, SEL name)
這個方法能夠爲那些 「徹底不知道具體實現」 的黑盒方法增長日誌記錄功能,這很是有助於程序調試。 如果濫用,反而會令代碼變的不易讀懂且難於維護。
method swizzling代碼:https://github.com/AlonerOwl/Runtime/tree/master/Runtime/MethodSwizzling