【OC刨根問底】-Runtime簡單粗暴理解

從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介紹

二、類的本質:
- 類相關:
+ 數據類型: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介紹

作用:在程序運行的時候執行編譯后的代碼,可以:
> 動態(創建)、(修改)、(內省) 方法
> 消息傳遞

運行時Runtime的一切都圍繞這兩個中心:類的動態配置 和 消息傳遞。通過操作函數來配置類信息,通過msgSend函數傳遞消息。
本質:libobjc.dylib,C和匯編(消息傳遞機制由匯編寫成)寫成。

二、類的本質:

1、類相關:

數據結構(本源):Class類型的結構體。在objc/runtime.h中查看其成員:

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 *):

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指向的方法。找到后即運行這個方法。

![metaClass.png](http://upload-images.jianshu.io/upload_images/666982-2a3d1f3bbe21c32c.png?imageMogr2/auto-orient/strip|imageView2/2/w/1240)
> 上圖是關于isa和super_class指針的圖解:
1、isa:實例對象->類->元類->(`不經過父元類`)直接到根元類(NSObject的元類),`根元類的isa指向自己`;
2、 superclass:類->父類->...->根類NSObject,`元類->父元類->...->根元類->根類`,NSObject的superclass指向nil。

b、操作函數:類對象以class_為前綴,實例對象以object_為前綴
- class_:
get: 類名,父類,元類;實例變量,成員變量;屬性;實例方法,類方法,方法實現;

```objc
// 獲取類的類名
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: 成員變量列表;屬性列表;方法列表;協議列表;

// 獲取整個成員變量列表
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: 成員變量;屬性;方法;協議;(添加成員變量只能在運行時創建的類,且不能為元類)

// 添加成員變量
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:屬性;方法;

// 替換類的屬性
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:響應方法判斷(內省)

// 類實例是否響應指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );

isMetaClass:元類判斷(內省)

// 判斷給定的Class是否是一個元類
BOOL class_isMetaClass ( Class cls );

conform:遵循協議判斷(內省)

// 返回類是否實現指定的協議
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
  • objc_:
    get: 實例變量;成員變量;類名;類;元類;關聯對象
// 獲取對象實例變量
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:對象;類;類列表;協議列表;

// 獲取指定對象的一份拷貝
id object_copy ( id obj, size_t size );
// 創建并返回一個指向所有已注冊類的指針列表
Class * objc_copyClassList ( unsigned int *outCount );


set: 實例變量;類;類列表;協議;關聯對象;

// 設置類實例的實例變量的值
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: 對象;

// 釋放指定對象占用的內存
id object_dispose ( id obj );

  • 動態創建/銷毀類、對象
    動態創建/銷毀類:
// 創建一個新類和元類
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );

// 銷毀一個類及其相關聯的類
void objc_disposeClassPair ( Class cls );

// 在應用中注冊由objc_allocateClassPair創建的類
void objc_registerClassPair ( Class cls );

動態創建/銷毀對象:

// 創建類實例
id class_createInstance ( Class cls, size_t extraBytes );

// 在指定位置創建類實例
id objc_constructInstance ( Class cls, void *bytes );

// 銷毀類實例
void * objc_destructInstance ( id obj );

2、實例變量、屬性相關:
實例變量和屬性也是類對象的關鍵配置。

屬性變量的意義就是方便讓其他對象訪問實例變量,另外可以拓展實例變量的作用范圍。當然,你可以設置只讀或者可寫等,設置方法也可自定義。

a、數據類型:
Ivar;

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還沒屬性);

typedef struct objc_property *objc_property_t;

objc_property_attribute_t(屬性的特性有:返回值、是否為atomic、getter/setter名字、是否為dynamic、背后使用的ivar名字、是否為弱引用等);

typedef struct {
    const char *name;           // 特性名
    const char *value;          // 特性值
} objc_property_attribute_t;

b、操作函數:

  • ivar_:
    get:
// 獲取成員變量名
const char * ivar_getName ( Ivar v );

// 獲取成員變量類型編碼
const char * ivar_getTypeEncoding ( Ivar v );

// 獲取成員變量的偏移量
ptrdiff_t ivar_getOffset ( Ivar v );
  • property_:
// 獲取屬性名
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編譯器指令來獲取它。

typedef struct objc_selector *SEL;

<objc/runtime.h>中沒有公開具體的objc_selector結構體成員。但通過log可知SEL本質是一個字符串。

IMP;
IMP是指向實現函數的指針,通過SEL取得IMP后,我們就獲得了最終要找的實現函數的入口。

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

Method
這個結構體相當于在SEL和IMP之間作了一個綁定。這樣有了SEL,我們便可以找到對應的IMP,從而調用方法的實現代碼。(在運行時才將SEL和IMP綁定, 動態配置方法)

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存儲了類的某個方法的信息。

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方法)。

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: 方法實現的返回值;
// 調用指定方法的實現
id method_invoke ( id receiver, Method m, ... );

// 調用返回一個數據結構的方法的實現
void method_invoke_stret ( id receiver, Method m, ... );

get: 方法名;方法實現;參數與返回值相關;

// 獲取方法名
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: 返回值類型,參數類型

// 獲取方法的返回值類型的字符串
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:方法實現;

// 設置方法的實現
IMP method_setImplementation ( Method m, IMP imp );

exchange:交換方法實現

// 交換兩個方法的實現
void method_exchangeImplementations ( Method m1, Method m2 );

description : 方法描述

// 返回指定方法的方法描述結構體
struct objc_method_description * method_getDescription ( Method m );

  • sel_
// 返回給定選擇器指定的方法的名稱
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的實際動作就是:找到這個函數指針,然后調用它。

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:方法,拋出異常。

unrecognized selector sent to instance

(消息轉發可以利用轉移消息接受對象,實現偽多重繼承的效果。)


4、 協議相關:@protocol聲明了可以被其他任何類實現的方法,協議僅僅是定義一個接口,而由其他的類去負責實現。

數據類型:Protocol;

typedef struct objc_object Protocol;


protocol是一個對象結構體。

操作函數:

  • objc_:
// 返回指定的協議
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: 協議;屬性;
// 返回協議名
const char * protocol_getName ( Protocol *p );
// 獲取協議的指定屬性
objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );

copy:協議列表;屬性列表;

// 獲取協議中的屬性列表
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );
// 獲取協議采用的協議
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );


add:屬性;方法;協議;

// 為協議添加方法
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 );


isEqual:判斷兩協議等同;

// 測試兩個協議是否相等
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );


comform:判斷是否遵循協議;

// 查看協議是否采用了另一個協議
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:
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指向的是相同的消息接收者。

struct objc_super {
   __unsafe_unretained id receiver;
   __unsafe_unretained Class super_class;
};

原理:使用super來接收消息時,編譯器會生成一個objc_super結構體。發送消息時,不是調用objc_msgSend函數,而是調用objc_msgSendSuper函數:

id objc_msgSendSuper ( struct objc_super *super, SEL op, ... );

該函數實際的操作是:從objc_super結構體指向的superClass的方法列表開始查找selector,找到后以objc->receiver去調用這個selector。

  • Runtime開源源碼對一些方法的實現:
- (Class)class ;

- (Class)class {
    return object_getClass(self);
}
+ (Class)class;

+ (Class)class {
    return self;
}
- (BOOL)isKindOf:aClass;// (for循環遍歷父類,每次判斷返回的結果可能不同)

- (BOOL)isKindOf:aClass
{
    Class cls;
    for (cls = isa; cls; cls = cls->superclass) 
        if (cls == (Class)aClass)
            return YES;
    return NO;
}
- (BOOL)isMemberOf:aClass;

- (BOOL)isMemberOf:aClass
{
    return isa == (Class)aClass;
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容

  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,757評論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,585評論 33 466
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,153評論 0 9
  • 前言 runtime其實在我們日常開發過程中很少使用到,尤其是像我現在比較初級的程序猿就更用不到了。但是去面試很多...
    WolfTin閱讀 652評論 0 2
  • 今天我要說的主題是朋友圈的文章有可能會害了你。 可能在現在的生活多,有很多人和我一樣,總感覺自己一天忙忙碌碌,沒什...
    蝙蝠女俠_TS閱讀 530評論 0 0