從C的程式導向到接觸OC的物件、訊息的過渡初期總會有知其然不知其所以然的糾結,相關的學習資源一般都是介紹有什麼、使用步驟一二三四的套路,這樣就很難知道知道本質是什麼,能幹什麼不能幹什麼,為什麼要選擇用它。而實際開發過程,都是先有什麼要解決,再努力找到實現方法。人腦的容易接受的資訊,也多是主幹到分枝的思維導圖,綱舉目張。所以,試著以自己的粗淺理解來寫一點關於OC執行時的東西。
程式碼的思想,大概是把重複且不變的東西封裝成可以重複利用的共性,把變化的東西細化為具體獨立鬆耦合的變數。這些可以是資料型別,也可以是實現的方法程式碼片段。類也是封裝的產物和可封裝的物件。被封裝的東西,需要找到裡面內容來具體地實現,就需要給裡面內容加個關聯的對映標識,比如索引(陣列)、字串(字典)、指標、SEL(方法的代號)、isa(物件)等等。大概來說就是用類和物件來封裝父類指標和方法列表,用對映來找到實現方法的程式碼片段。
主要思路:
例項物件instance->類class->方法method(->SEL->IMP)->實現函式
例項物件只存放isa指標和例項變數,由isa指標找到所屬類,類維護一個執行時可接收的方法列表
;方法列表中的每個入口是一個方法(Method)
,其中key是一個特定名稱,即選擇器(SEL)
,其對應一個指向底層C實現函式的指標,即實現(IMP)
,。執行時機制最關鍵核心是objc_msgSend函式,通過給target(類)傳送selecter(SEL)來傳遞訊息,找到匹配的IMP,指向實現的C函式。
由於OC的執行時動態特性,在編譯之後可以在執行時通過C操作函式,動態地建立修改類資訊,動態繫結方法和重寫實現,靈活地實現一些自定義功能。
紙上寫了個大綱,沒有畫思維導圖,簡單列個目錄:
一、執行時Runtime介紹
二、類的本質:
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 38 39 40 41 42 43 44 45 46 47 |
- 類相關: + 資料型別:class,object; - isa 元類 - superClass 根類 + 操作函式: - class_: + get: 類名,父類; 例項變數,成員變數;屬性;例項方法,類方法,方法實現; + copy: 成員變數列表;屬性列表;方法列表;協議列表; + add: 成員變數;屬性;方法;協議; + replace:屬性;方法; + respond:響應方法判斷(內省) + isMetaclass:元類判斷(內省) + conform:遵循協議判斷(內省) - objc_: + get: 例項變數;成員變數;類名;類;元類;關聯物件; + copy: 物件;類;類列表;協議列表; + set: 例項變數;成員變數;類;類列表;協議;關聯物件; + dispose: 物件; - 動態建立/銷燬類、物件 - 成員變數、屬性相關: + 資料型別:Ivar;objc_property_t;objc_property_attribute_t; + 操作函式: - ivar_: - property_: - 方法訊息相關: + 資料型別:SEL;IMP; Method;方法快取 + 操作函式: - method_: + invoke: 方法實現的返回值; + get: 方法名;方法實現;引數與返回值相關; + set:方法實現; + exchange:交換方法實現 + 方法呼叫:msgSend函式(找到方法實現) + 訊息轉發: - Method Resolution - Fast Forwarding - Normal Forwarding - 協議相關: + 資料型別:Protocol; + 操作函式: - protocol_: + get: 協議;屬性; + copy:協議列表;屬性列表; + add:屬性;方法;協議; + isEqual:判斷兩協議等同; + comform:判斷是否遵循協議; - 其他:類名;版本號;類資訊;(忽略) |
三、 動態實現:
- Method Swizzling;
- ISA Swizzling;
四、 其他概念:category;super;等等。想起來再加…
————進入正題———–
一、執行時Runtime介紹
作用:在程式執行的時候執行編譯後的程式碼,可以:
1 2 |
> 動態(建立)、(修改)、(內省) `類`和`方法` > 訊息傳遞 |
執行時Runtime的一切都圍繞這兩個中心:類的動態配置 和 訊息傳遞。通過操作函式來配置類資訊,通過msgSend函式傳遞訊息。
本質:libobjc.dylib,C和彙編(訊息傳遞機制由彙編寫成)寫成。
二、類的本質:
1、類相關:
資料結構(本源):Class型別的結構體。在objc/runtime.h中檢視其成員:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #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; |
a、資料型別:
isa和super_class
:不同的類中可以有相同的方法(同一個類的方法不能同名,哪怕引數型別不同,後面解釋…),所以要先確定是那個類。isa和super_class是找到實現函式的關鍵對映,決定找到存放在哪個類的方法實現。(isa用於自省確定所屬類,super_class確定繼承關係)。
例項物件的isa指標指向類,類的isa指標指向其元類(metaClass)。物件就是一個含isa指標的結構體。類儲存例項物件的方法列表,元類儲存類的方法列表,元類也是類物件。
這是id型別的結構(類似於C裡面的void *):
1 2 3 4 5 |
struct objc_object { Class isa OBJC_ISA_AVAILABILITY; }; typedef struct objc_object *id; |
當建立例項物件時,分配的記憶體包含一個objc_object資料結構,然後是類到父類直到根類NSObject的例項變數的資料。NSObject類的alloc和allocWithZone:方法使用函式class_createInstance來建立objc_object資料結構。
向一個Objective-C物件傳送訊息時,執行時庫會根據例項物件的isa
指標找到這個例項物件所屬的類。Runtime庫會在類的方法列表由super_class
指標找到父類的方法列表直至根類NSObject中去尋找與訊息對應的selector指向的方法。找到後即執行這個方法。
123 上圖是關於isa和super_class指標的圖解:1、isa:例項物件->類->元類->(不經過父元類)直接到根元類(NSObject的元類),根元類的isa指向自己;2、 superclass:類->父類->...->根類NSObject,元類->父元類->...->根元類->根類,NSObject的superclass指向nil。
b、操作函式:類物件以class_為字首,例項物件以object_為字首
- class_:
get: 類名,父類,元類;例項變數,成員變數;屬性;例項方法,類方法,方法實現;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 獲取類的類名 const char * class_getName ( Class cls ); // 獲取類的父類 Class class_getSuperclass ( Class cls ); // 獲取例項大小 size_t class_getInstanceSize ( Class cls ); // 獲取類中指定名稱例項成員變數的資訊 Ivar class_getInstanceVariable ( Class cls, const char *name ); // 獲取類成員變數的資訊 Ivar class_getClassVariable ( Class cls, const char *name ); // 獲取指定的屬性 objc_property_t class_getProperty ( Class cls, const char *name ); // 獲取例項方法 Method class_getInstanceMethod ( Class cls, SEL name ); // 獲取類方法 Method class_getClassMethod ( Class cls, SEL name ); // 獲取方法的具體實現 IMP class_getMethodImplementation ( Class cls, SEL name ); IMP class_getMethodImplementation_stret ( Class cls, SEL name ); |
copy: 成員變數列表;屬性列表;方法列表;協議列表;
1 2 3 4 5 6 7 8 |
// 獲取整個成員變數列表 Ivar * class_copyIvarList ( Class cls, unsigned int *outCount ); // 獲取屬性列表 objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount ); // 獲取所有方法的列表 Method * class_copyMethodList ( Class cls, unsigned int *outCount ); // 獲取類實現的協議列表 Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount ); |
add: 成員變數;屬性;方法;協議;(新增成員變數只能在執行時建立的類,且不能為元類)
1 2 3 4 5 6 7 8 |
// 新增成員變數 BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types ); // 新增屬性 BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount ); // 新增方法 BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types ); // 新增協議 BOOL class_addProtocol ( Class cls, Protocol *protocol ); |
replace:屬性;方法;
1 2 3 4 |
// 替換類的屬性 void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount ); // 替代方法的實現 IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types ); |
respond:響應方法判斷(內省)
1 2 |
// 類例項是否響應指定的selector BOOL class_respondsToSelector ( Class cls, SEL sel ); |
isMetaClass:元類判斷(內省)
1 2 |
// 判斷給定的Class是否是一個元類 BOOL class_isMetaClass ( Class cls ); |
conform:遵循協議判斷(內省)
1 2 |
// 返回類是否實現指定的協議 BOOL class_conformsToProtocol ( Class cls, Protocol *protocol ); |
- objc_:
get: 例項變數;成員變數;類名;類;元類;關聯物件
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 獲取物件例項變數 Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue ); // 獲取物件中例項變數的值 id object_getIvar ( id obj, Ivar ivar ); // 獲取物件的類名 const char * object_getClassName ( id obj ); // 獲取物件的類 Class object_getClass ( id obj ); Class objc_getClass ( const char *name ); // 返回指定類的元類 Class objc_getMetaClass ( const char *name ); //獲取關聯物件 id objc_getAssociatedObject(self, &myKey); |
copy:物件;類;類列表;協議列表;
1 2 3 4 |
// 獲取指定物件的一份拷貝 id object_copy ( id obj, size_t size ); // 建立並返回一個指向所有已註冊類的指標列表 Class * objc_copyClassList ( unsigned int *outCount ); |
set: 例項變數;類;類列表;協議;關聯物件;
1 2 3 4 5 6 |
// 設定類例項的例項變數的值 Ivar object_setInstanceVariable ( id obj, const char *name, void *value ); // 設定物件中例項變數的值 void object_setIvar ( id obj, Ivar ivar, id value ); //設定關聯物件 void objc_setAssociatedObject(self, &myKey, anObject, OBJC_ASSOCIATION_RETAIN); |
dispose: 物件;
1 2 |
// 釋放指定物件佔用的記憶體 id object_dispose ( id obj ); |
- 動態建立/銷燬類、物件
動態建立/銷燬類:
1 2 3 4 5 6 7 8 |
// 建立一個新類和元類 Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes ); // 銷燬一個類及其相關聯的類 void objc_disposeClassPair ( Class cls ); // 在應用中註冊由objc_allocateClassPair建立的類 void objc_registerClassPair ( Class cls ); |
動態建立/銷燬物件:
1 2 3 4 5 6 7 8 |
// 建立類例項 id class_createInstance ( Class cls, size_t extraBytes ); // 在指定位置建立類例項 id objc_constructInstance ( Class cls, void *bytes ); // 銷燬類例項 void * objc_destructInstance ( id obj ); |
2、例項變數、屬性相關:
例項變數和屬性也是類物件的關鍵配置。
屬性變數的意義就是方便讓其他物件訪問例項變數,另外可以擴充例項變數的作用範圍。當然,你可以設定只讀或者可寫等,設定方法也可自定義。
a、資料型別:
Ivar;
1 2 3 4 5 6 7 8 9 10 |
typedef struct objc_ivar *Ivar; struct objc_ivar { char *ivar_name OBJC2_UNAVAILABLE; // 變數名 char *ivar_type OBJC2_UNAVAILABLE; // 變數型別 int ivar_offset OBJC2_UNAVAILABLE; // 基地址偏移位元組 #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif } |
objc_property_t(取名可能是因為當時Objective-C1.0還沒屬性);
1 |
typedef struct objc_property *objc_property_t; |
objc_property_attribute_t(屬性的特性有:返回值、是否為atomic、getter/setter名字、是否為dynamic、背後使用的ivar名字、是否為弱引用等);
1 2 3 4 |
typedef struct { const char *name; // 特性名 const char *value; // 特性值 } objc_property_attribute_t; |
b、操作函式:
- ivar_:
get:
1 2 3 4 5 6 7 8 |
// 獲取成員變數名 const char * ivar_getName ( Ivar v ); // 獲取成員變數型別編碼 const char * ivar_getTypeEncoding ( Ivar v ); // 獲取成員變數的偏移量 ptrdiff_t ivar_getOffset ( Ivar v ); |
- property_:
1 2 3 4 5 6 7 8 9 10 11 |
// 獲取屬性名 const char * property_getName ( objc_property_t property ); // 獲取屬性特性描述字串 const char * property_getAttributes ( objc_property_t property ); // 獲取屬性中指定的特性 char * property_copyAttributeValue ( objc_property_t property, const char *attributeName ); // 獲取屬性的特性列表 objc_property_attribute_t * property_copyAttributeList ( objc_property_t property, unsigned int *outCount ); |
3、 方法訊息相關:
訊息傳遞機制是Runtime的核心,也即訊息分派器objc_msgSend。先要知道幾個概念。
a、 資料型別:
SEL
;
SEL又叫選擇器,是表示一個方法的selector的指標,對映方法的名字。Objective-C在編譯時,會依據每一個方法的名字、引數序列,生成一個唯一的整型標識(Int型別的地址),這個標識就是SEL。
SEL的作用是作為IMP的KEY,儲存在NSSet中,便於hash快速查詢方法。SEL不能相同,對應方法可以不同。所以在Objective-C同一個類(及類的繼承體系)中,不能存在2個同名的方法,就算引數型別不同。多個方法可以有同一個SEL。
不同的類可以有相同的方法名。不同類的例項物件執行相同的selector時,會在各自的方法列表中去根據selector去尋找自己對應的IMP。
相關概念:型別編碼(Type Encoding)
編譯器將每個方法的返回值和引數型別編碼為一個字串,並將其與方法的selector關聯在一起。可以使用@encode編譯器指令來獲取它。
1 |
typedef struct objc_selector *SEL; |
中沒有公開具體的objc_selector結構體成員。但通過log可知SEL本質是一個字串。
IMP
;
IMP是指向實現函式的指標,通過SEL取得IMP後,我們就獲得了最終要找的實現函式的入口。
1 |
typedefine id (*IMP)(id, SEL, ...) |
1 |
Method; |
這個結構體相當於在SEL和IMP之間作了一個繫結。這樣有了SEL,我們便可以找到對應的IMP,從而呼叫方法的實現程式碼。(在執行時才將SEL和IMP繫結, 動態配置方法)
1 2 3 4 5 6 7 |
typedef struct objc_method *Method; struct objc_method { SEL method_name OBJC2_UNAVAILABLE; // 方法名 char *method_types OBJC2_UNAVAILABLE; // 引數型別 IMP method_imp OBJC2_UNAVAILABLE; // 方法實現 } |
objc_method_list 就是用來儲存當前類的方法連結串列,objc_method儲存了類的某個方法的資訊。
1 2 3 4 5 6 7 8 9 |
struct objc_method_list { struct objc_method_list *obsolete OBJC2_UNAVAILABLE; int method_count OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif /* variable length structure */ struct objc_method method_list[1] OBJC2_UNAVAILABLE; } |
方法快取
;
方法呼叫最先是在方法快取裡找的,方法呼叫是懶呼叫,第一次呼叫時載入後加到快取池裡。一個objc程式啟動後,需要進行類的初始化、呼叫方法時的cache初始化,再傳送訊息的時候就直接走快取(引申:+load方法和+initialize方法。load方法是首次載入類時呼叫,絕對只呼叫一次;initialize方法是首次給類發訊息時呼叫,通常只呼叫一次,但如果它的子類初始化時未定義initialize方法,則會再呼叫一次它的initialize方法)。
1 2 3 4 5 6 7 8 9 |
struct objc_cache { // 快取bucket的總數 unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE; // 實際快取bucket的總數 unsigned int occupied OBJC2_UNAVAILABLE; // 指向Method資料結構指標的陣列 Method buckets[1] OBJC2_UNAVAILABLE; }; |
b、 操作函式:
- method_:
invoke: 方法實現的返回值;
1 2 3 4 5 |
// 呼叫指定方法的實現 id method_invoke ( id receiver, Method m, ... ); // 呼叫返回一個資料結構的方法的實現 void method_invoke_stret ( id receiver, Method m, ... ); |
get: 方法名;方法實現;引數與返回值相關;
1 2 3 4 5 6 7 8 9 10 11 |
// 獲取方法名 SEL method_getName ( Method m ); // 返回方法的實現 IMP method_getImplementation ( Method m ); // 獲取描述方法引數和返回值型別的字串 const char * method_getTypeEncoding ( Method m ); // 返回方法的引數的個數 unsigned int method_getNumberOfArguments ( Method m ); // 通過引用返回方法指定位置引數的型別字串 void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len ); |
copy: 返回值型別,引數型別
1 2 3 4 5 6 7 8 |
// 獲取方法的返回值型別的字串 char * method_copyReturnType ( Method m ); // 獲取方法的指定位置引數的型別字串 char * method_copyArgumentType ( Method m, unsigned int index ); // 通過引用返回方法的返回值型別字串 void method_getReturnType ( Method m, char *dst, size_t dst_len ); |
set:方法實現;
1 2 |
// 設定方法的實現 IMP method_setImplementation ( Method m, IMP imp ); |
exchange:交換方法實現
1 2 |
// 交換兩個方法的實現 void method_exchangeImplementations ( Method m1, Method m2 ); |
description : 方法描述
1 2 |
// 返回指定方法的方法描述結構體 struct objc_method_description * method_getDescription ( Method m ); |
- sel_
1 2 3 4 5 6 7 8 9 10 11 |
// 返回給定選擇器指定的方法的名稱 const char * sel_getName ( SEL sel ); // 在Objective-C Runtime系統中註冊一個方法,將方法名對映到一個選擇器,並返回這個選擇器 SEL sel_registerName ( const char *str ); // 在Objective-C Runtime系統中註冊一個方法 SEL sel_getUid ( const char *str ); // 比較兩個選擇器 BOOL sel_isEqual ( SEL lhs, SEL rhs ); |
c、方法呼叫流程
:向物件傳送訊息,實際上是呼叫objc_msgSend函式,obj_msgSend的實際動作就是:找到這個函式指標,然後呼叫它。
1 |
id objc_msgSend(receiver self, selector _cmd, arg1, arg2, ...) |
self和_cmd是隱藏引數,在編譯期被插入實現程式碼。
self:指向訊息的接受者target的物件型別,作為一個佔位引數,訊息傳遞成功後self將指向訊息的receiver。
_cmd: 指向方法實現的SEL型別。
當向一般物件傳送訊息時,呼叫objc_msgSend;當向super傳送訊息時,呼叫的是objc_msgSendSuper; 如果返回值是一個結構體,則會呼叫objc_msgSend_stret或objc_msgSendSuper_stret。
0.1-檢查target是否為nil。如果為nil,直接cleanup,然後return。(這就是我們可以向nil傳送訊息的原因。) 如果方法返回值是一個物件,那麼傳送給nil的訊息將返回nil;如果方法返回值為指標型別,其指標大小為小於或者等於sizeof(void*),float,double,long double 或者long long的整型標量,傳送給nil的訊息將返回0;如果方法返回值為結構體,傳送給nil的訊息將返回0。結構體中各個欄位的值將都是0;如果方法的返回值不是上述提到的幾種情況,那麼傳送給nil的訊息的返回值將是未定義的。 0.2-如果target非nil,在target的Class中根據Selector去找IMP。(因為同一個方法可能在不同的類中有不同的實現,所以我們需要依賴於接收者的類來找到的確切的實現)。
1-首先它找到selector對應的方法實現: *1.1-在target類的方法快取列表裡檢查有沒有對應的方法實現,有的話,直接呼叫。 *1.2-比較請求的selector和類方法列表中的selector,對應的話,直接呼叫。 *1.3-比較請求的selector和父類方法列表,父類的父類,直至根類,如果有對應,則直接呼叫。(方法重寫攔截父類方法的原理) 2-呼叫方法實現,並將接收者物件及方法的所有引數傳給它。 3-最後,將實現函式的返回值作為自己的返回值。
d、動態方法解析與訊息轉發
:如果以上的類中沒有找到對應的selector(一般保險起見先用respondsToSelector:內省判斷):,還可以利用訊息轉發機制依次執行以下流程:
- Method Resolution(動態方法解析):
用所屬類的類方法+(BOOL)resolveInstanceMethod:(例項方法)或者+(BOOL)resolveClassMethod:(類方法),在此方法裡新增class_addMethod函式。一般用於@dynamic動態屬性。(當一個屬性宣告為@dynamic,就是向編譯器保證編譯時不用管/get實現,一定會在執行時實現)。 - Fast Forwarding (快速訊息轉發):
如果上一步無法響應訊息,呼叫- (id)forwardingTargetForSelector:(SEL)aSelector方法,將訊息接受者轉發到另一個物件target(不能為self,否則死迴圈)。 - Normal Forwarding(普通訊息轉發):
如果上一步無法響應訊息:
呼叫方法簽名- (NSMethodSignature )methodSignatureForSelector:(SEL)aSelector,方法簽名目的將函式的引數型別和返回值封裝;
如果返回非nil,則建立一個NSInvocation物件利用方法簽名和selector封裝未被處理的訊息,作為引數傳遞給- (void)forwardInvocation:(NSInvocation )anInvocation。
這一步比較耗時。
如果以上步驟(訊息傳遞和訊息轉發)還是不能響應訊息,則調動doesNotRecognizeSelector:方法,丟擲異常。
1 |
unrecognized selector sent to instance |
(訊息轉發可以利用轉移訊息接受物件,實現偽多重繼承的效果。)
4、 協議相關:@protocol宣告瞭可以被其他任何類實現的方法,協議僅僅是定義一個介面,而由其他的類去負責實現。
資料型別:Protocol;
1 |
typedef struct objc_object Protocol; |
protocol是一個物件結構體。
操作函式:
- objc_:
1 2 3 4 5 6 7 8 9 10 11 |
// 返回指定的協議 Protocol * objc_getProtocol ( const char *name ); // 獲取執行時所知道的所有協議的陣列 Protocol ** objc_copyProtocolList ( unsigned int *outCount ); // 建立新的協議例項 Protocol * objc_allocateProtocol ( const char *name ); // 在執行時中註冊新建立的協議 void objc_registerProtocol ( Protocol *proto ); |
- protocol_:
get: 協議;屬性;
1 2 3 4 |
// 返回協議名 const char * protocol_getName ( Protocol *p ); // 獲取協議的指定屬性 objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty ); |
copy:協議列表;屬性列表;
1 2 3 4 |
// 獲取協議中的屬性列表 objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount ); // 獲取協議採用的協議 Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount ); |
add:屬性;方法;協議;
1 2 3 4 5 6 7 8 |
// 為協議新增方法 void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod ); // 新增一個已註冊的協議到協議中 void protocol_addProtocol ( Protocol *proto, Protocol *addition ); // 為協議新增屬性 void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty ); |
1 |
isEqual:判斷兩協議等同; |
1 2 |
// 測試兩個協議是否相等 BOOL protocol_isEqual ( Protocol *proto, Protocol *other ); |
comform:判斷是否遵循協議;
1 2 |
// 檢視協議是否採用了另一個協議 BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other ); |
5、 其他:類名;版本號;類資訊;(忽略)
三、 動態實現:
- Method Swizzling;
Method Swizzling可以在執行時通過修改類的方法列表中selector對應的函式或者設定交換方法實現,來動態修改方法。可以重寫某個方法而不用繼承,同時還可以呼叫原先的實現。通常應用於在category中新增一個方法。
為保證改變方法引起衝突,確保方法混用只能一次性:
比如,在+load方法或者dispatch_once中執行。 - ISA Swizzling;
ISA Swizzling可以動態修改物件的isa指標,改變物件的類,類似於建立子類實現相同的功能。KVO即是同過ISA Swizzling實現的。
四、 其他概念:category;super;
- category:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
typedef struct objc_category *Category; struct objc_category { char *category_name OBJC2_UNAVAILABLE; // 分類名 char *class_name OBJC2_UNAVAILABLE; // 分類所屬的類名 struct objc_method_list *instance_methods OBJC2_UNAVAILABLE; // 例項方法列表 struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 類方法列表 struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分類所實現的協議列表 } // objc-runtime-new.h中定義: struct category_t { const char *name; // name 是指 class_name 而不是 category_name classref_t cls; // cls是要擴充套件的類物件,編譯期間是不會定義的,而是在Runtime階段通過name對應到對應的類物件 struct method_list_t *instanceMethods; struct method_list_t *classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; // instanceProperties表示Category裡所有的properties,(這就是我們可以通過objc_setAssociatedObject和objc_getAssociatedObject增加例項變數的原因,)不過這個和一般的例項變數是不一樣的 }; |
category就是定義方法的結構體,instance_methods列表是objc_class中方法列表的一個子集,class_methods列表是元類方法列表的一個子集。由其結構成員可知,category為什麼不能新增成員變數(可新增屬性,只有set/get方法)。
給category新增方法後,category_list會生成method list。這個方法列表是倒序新增的,也就是說,新生成的category的方法會先於舊的category的方法插入。(category的方法會優先於類方法執行)。
- super:
super並不是隱藏引數,它實際上只是一個”編譯器標示符”,它負責告訴編譯器,當呼叫方法時,跳過當前類去呼叫父類的方法,而不是本類中的方法。self是類的一個隱藏引數,每個方法的實現的第一個引數即為self。實際上給super發訊息時,super還是與self指向的是相同的訊息接收者。
1 2 3 4 |
struct objc_super { __unsafe_unretained id receiver; __unsafe_unretained Class super_class; }; |
原理:使用super來接收訊息時,編譯器會生成一個objc_super結構體。傳送訊息時,不是呼叫objc_msgSend函式,而是呼叫objc_msgSendSuper函式:
1 |
id objc_msgSendSuper ( struct objc_super *super, SEL op, ... ); |
該函式實際的操作是:從objc_super結構體指向的superClass的方法列表開始查詢selector,找到後以objc->receiver去呼叫這個selector。
- Runtime開源原始碼對一些方法的實現:
1 2 3 4 5 |
- (Class)class ; - (Class)class { return object_getClass(self); } |
1 2 3 4 5 |
+ (Class)class; + (Class)class { return self; } |
1 2 3 4 5 6 7 8 9 10 |
- (BOOL)isKindOf:aClass;// (for迴圈遍歷父類,每次判斷返回的結果可能不同) - (BOOL)isKindOf:aClass { Class cls; for (cls = isa; cls; cls = cls->superclass) if (cls == (Class)aClass) return YES; return NO; } |
1 2 3 4 5 6 |
- (BOOL)isMemberOf:aClass; - (BOOL)isMemberOf:aClass { return isa == (Class)aClass; } |