iOS開發(fā)·runtime原理與實踐: 基本知識篇(類衅鹿,超類,元類过咬,super_class,isa制妄,對象掸绞,方法,SEL耕捞,IMP)

摘要:這篇文章首先介紹runtime原理衔掸,包括類,超類俺抽,元類敞映,super_class,isa磷斧,對象振愿,方法,SEL弛饭,IMP等概念冕末,同時分別介紹與這些概念有關的API。接著介紹方法調用流程侣颂,以及尋找IMP的過程档桃。然后,介紹一下這些API的常見用法憔晒,并介紹runtime的冷門知識藻肄。最后介紹一下runtime的實戰(zhàn)指南。

Tips:蘋果公開的源代碼在這里可以查拒担,https://opensource.apple.com/tarballs/

例如嘹屯,其中,有兩個比較常見需要學習源碼的地址:

當然澎蛛,如果你想在github上在線查看源代碼抚垄,可以點這里:runtimerunloop

1. 運行時

1.1 基本概念: 運行時

Runtime 的概念

Runtime 又叫運行時,是一套底層的 C 語言 API呆馁,其為 iOS 內(nèi)部的核心之一桐经,我們平時編寫的 OC 代碼,底層都是基于它來實現(xiàn)的浙滤。比如:

// 發(fā)送消息
[receiver message];
// 底層運行時會被編譯器轉化為:
objc_msgSend(receiver, selector)
// 如果其還有參數(shù)比如:
[receiver message:(id)arg...];
// 底層運行時會被編譯器轉化為:
objc_msgSend(receiver, selector, arg1, arg2, ...)

以上你可能看不出它的價值阴挣,但是我們需要了解的是 Objective-C 是一門動態(tài)語言,它會將一些工作放在代碼運行時才處理而并非編譯時纺腊。也就是說畔咧,有很多類和成員變量在我們編譯的時是不知道的,而在運行時揖膜,我們所編寫的代碼會轉換成完整的確定的代碼運行誓沸。

因此,編譯器是不夠的壹粟,我們還需要一個運行時系統(tǒng)(Runtime system)來處理編譯后的代碼拜隧。Runtime 基本是用 C 和匯編寫的,由此可見蘋果為了動態(tài)系統(tǒng)的高效而做出的努力趁仙。蘋果和 GNU 各自維護一個開源的 Runtime 版本洪添,這兩個版本之間都在努力保持一致。

Runtime 的作用

Objc 在三種層面上與 Runtime 系統(tǒng)進行交互:

  1. 通過 Objective-C 源代碼
  2. 通過 Foundation 框架的 NSObject 類定義的方法
  3. 通過對 Runtime 庫函數(shù)的直接調用

1.2 各種基本概念的C表達

在 Objective-C 中雀费,類干奢、對象和方法都是一個 C 的結構體,從 objc/objc.h(對象盏袄,objc_object忿峻,id)以及objc/runtime.h(其它,類辕羽,方法炭菌,方法列表,變量列表逛漫,屬性列表等相關的)以及中黑低,我們可以找到他們的定義。

① 類

類對象(Class)是由程序員定義并在運行時由編譯器創(chuàng)建的酌毡,它沒有自己的實例變量克握,這里需要注意的是類的成員變量實例方法列表是屬于實例對象的,但其存儲于類對象當中的枷踏。我們在objc/objc.h下看看Class的定義:

  • Class
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

可以看到類是由Class類型來表示的菩暗,它是一個objc_class結構類型的指針。我們接著來看objc_class結構體的定義:

  • objc_class
struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */

參數(shù)解析

  • isa指針是和Class同類型的objc_class結構指針旭蠕,類對象的指針指向其所屬的類丹墨,即元類。元類中存儲著類對象的類方法庸诱,當訪問某個類的類方法時會通過該isa指針從元類中尋找方法對應的函數(shù)指針。
  • super_class指針指向該類所繼承的父類對象秒梅,如果該類已經(jīng)是最頂層的根類(如NSObject或NSProxy), 則 super_class為NULL。
  • cache:用于緩存最近使用的方法舌胶。一個接收者對象接收到一個消息時捆蜀,它會根據(jù)isa指針去查找能夠響應這個消息的對象。在實際使用中幔嫂,這個對象只有一部分方法是常用的辆它,很多方法其實很少用或者根本用不上。這種情況下履恩,如果每次消息來時锰茉,我們都是methodLists中遍歷一遍,性能勢必很差切心。這時洞辣,cache就派上用場了。在我們每次調用過一個方法后昙衅,這個方法就會被緩存到cache列表中,下次調用的時候runtime就會優(yōu)先去cache中查找定鸟,如果cache沒有而涉,才去methodLists中查找方法。這樣联予,對于那些經(jīng)常用到的方法的調用啼县,但提高了調用的效率。

  • version:我們可以使用這個字段來提供類的版本信息沸久。這對于對象的序列化非常有用季眷,它可是讓我們識別出不同類定義版本中實例變量布局的改變。

  • protocols:當然可以看出這一個objc_protocol_list的指針卷胯。關于objc_protocol_list的結構體構成后面會講子刮。

獲取類的類名

// 獲取類的類名
const char * class_getName ( Class cls ); 

動態(tài)創(chuàng)建類

// 創(chuàng)建一個新類和元類
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes ); //如果創(chuàng)建的是root class,則superclass為Nil窑睁。extraBytes通常為0

// 銷毀一個類及其相關聯(lián)的類
void objc_disposeClassPair ( Class cls ); //在運行中還存在或存在子類實例挺峡,就不能夠調用這個。

// 在應用中注冊由objc_allocateClassPair創(chuàng)建的類
void objc_registerClassPair ( Class cls ); //創(chuàng)建了新類后担钮,然后使用class_addMethod橱赠,class_addIvar函數(shù)為新類添加方法,實例變量和屬性后再調用這個來注冊類箫津,再之后就能夠用了狭姨。
② 對象

實例對象是我們對類對象alloc或者new操作時所創(chuàng)建的宰啦,在這個過程中會拷貝實例所屬的類的成員變量,但并不拷貝類定義的方法饼拍。調用實例方法時赡模,系統(tǒng)會根據(jù)實例的isa指針去類的方法列表及父類的方法列表中尋找與消息對應的selector指向的方法。同樣的惕耕,我們也來看下其定義:

  • objc_object
/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

可以看到纺裁,這個結構體只有一個isa變量,指向實例對象所屬的司澎。任何帶有以指針開始并指向類結構的結構都可以被視作objc_object, 對象最重要的特點是可以給其發(fā)送消息欺缘。 NSObject類的allocallocWithZone:方法使用函數(shù)class_createInstance來創(chuàng)建objc_object數(shù)據(jù)結構。

另外我們常見的id類型挤安,它是一個objc_object結構類型的指針谚殊。該類型的對象可以轉換為任何一種對象,類似于C語言中void *指針類型的作用蛤铜。其定義如下所示:

  • id
/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif

對對象的類操作

// 返回給定對象的類名
const char * object_getClassName ( id obj );
// 返回對象的類
Class object_getClass ( id obj );
// 設置對象的類
Class object_setClass ( id obj, Class cls );

獲取對象的類定義

// 獲取已注冊的類定義的列表
int objc_getClassList ( Class *buffer, int bufferCount );

// 創(chuàng)建并返回一個指向所有已注冊類的指針列表
Class * objc_copyClassList ( unsigned int *outCount );

// 返回指定類的類定義
Class objc_lookUpClass ( const char *name );
Class objc_getClass ( const char *name );
Class objc_getRequiredClass ( const char *name );

// 返回指定類的元類
Class objc_getMetaClass ( const char *name );

動態(tài)創(chuàng)建對象

// 創(chuàng)建類實例
id class_createInstance ( Class cls, size_t extraBytes ); //會在heap里給類分配內(nèi)存嫩絮。這個方法和+alloc方法類似。

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

// 銷毀類實例
void * objc_destructInstance ( id obj ); //不會釋放移除任何相關引用
③ 元類

元類(Metaclass)就是類對象围肥,每個都有自己的元類剿干,也就是objc_class結構體里面isa指針所指向的類. Objective-C的類方法是使用元類的根本原因,因為其中存儲著對應的類對象調用的方法即類方法穆刻。

當向對象發(fā)消息置尔,runtime會在這個對象所屬類方法列表中查找發(fā)送消息對應的方法,但當向類發(fā)送消息時氢伟,runtime就會在這個類的meta class方法列表里查找榜轿。所有的meta class,包括Root class朵锣,Superclass谬盐,Subclass的isa都指向Root class的meta class,這樣能夠形成一個閉環(huán)诚些。

所以由上圖可以看到飞傀,在給實例對象或類對象發(fā)送消息時,尋找方法列表的規(guī)則為:

  • 當發(fā)送消息給實例對象時诬烹,消息是在尋找這個對象的類的方法列表(實例方法)
  • 當發(fā)送消息給類對象時助析,消息是在尋找這個類的元類的方法列表(類方法)

元類,就像之前的類一樣椅您,它也是一個對象外冀,也可以調用它的方法。所以這就意味著它必須也有一個類掀泳。所有的元類都使用根元類作為他們的類雪隧。比如所有NSObject的子類的元類都會以NSObject的元類作為他們的類西轩。

根據(jù)這個規(guī)則,所有的元類使用根元類作為他們的類脑沿,根元類的元類則就是它自己藕畔。也就是說基類的元類的isa指針指向他自己。

操作函數(shù)

  • super_class和meta-class
// 獲取類的父類
Class class_getSuperclass ( Class cls );
// 判斷給定的Class是否是一個meta class
BOOL class_isMetaClass ( Class cls );
  • instance_size
// 獲取實例大小
size_t class_getInstanceSize ( Class cls );
④ 屬性

在Objective-C中庄拇,屬性(property)和成員變量是不同的注服。那么,屬性的本質是什么措近?它和成員變量之間有什么區(qū)別溶弟?簡單來說屬性是添加了存取方法的成員變量,也就是:

@property = ivar + getter + setter;

因此瞭郑,我們每定義一個@property都會添加對應的ivar, getter和setter到類結構體objc_class中辜御。具體來說,系統(tǒng)會在objc_ivar_list中添加一個成員變量的描述屈张,然后在methodLists中分別添加setter和getter方法的描述擒权。下面的objc_property_t是聲明的屬性的類型,是一個指向objc_property結構體的指針阁谆。

用法舉例

//遍歷獲取所有屬性Property
- (void) getAllProperty {
    unsigned int propertyCount = 0;
    objc_property_t *propertyList = class_copyPropertyList([Person class], &propertyCount);
    for (unsigned int i = 0; i < propertyCount; i++ ) {
        objc_property_t *thisProperty = propertyList[i];
        const char* propertyName = property_getName(*thisProperty);
        NSLog(@"Person擁有的屬性為: '%s'", propertyName);
    }
}
  • objc_property_t
/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;

另外碳抄,關于屬性有一個objc_property_attribute_t結構體列表,objc_property_attribute_t結構體包含namevalue

  • objc_property_attribute_t
typedef struct {
    const char * _Nonnull name;           /**< The name of the attribute */
    const char * _Nonnull value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

常用的屬性如下:

  • 屬性類型 name值:T value:變化
  • 編碼類型 name值:C(copy) &(strong) W(weak)空(assign) 等 value:無
  • 非/原子性 name值:空(atomic) N(Nonatomic) value:無
  • 變量名稱 name值:V value:變化

例如

@interface person : NSObjec{  
  NSString *_name;  
}  
int main(){  
  objc_property_attribute_t nonatomic = {"N", ""};  
  objc_property_attribute_t strong = {"&", ""};  
  objc_property_attribute_t type = {"T", "@\"NSString\""};  
  objc_property_attribute_t ivar = {"V", "_name"};  
  objc_property_attribute_t attributes[] = {nonatomic, strong, type, ivar};  
  BOOL result = class_addProperty([person class], "name", attributes, 4);  
}  

操作函數(shù)

// 獲取屬性名
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 );
⑤ 成員變量

Ivar: 實例變量類型场绿,是一個指向objc_ivar結構體的指針

  • Ivar
/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

objc_ivar結構體的組成如下:

  • objc_ivar
- struct objc_ivar {
    char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
    char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}   

這里我們注意第三個成員 ivar_offset剖效。它表示基地址偏移字節(jié)。

操作函數(shù)

//成員變量操作函數(shù)
// 修改類實例的實例變量的值
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
// 獲取對象實例變量的值
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
// 返回指向給定對象分配的任何額外字節(jié)的指針
void * object_getIndexedIvars ( id obj );
// 返回對象中實例變量的值
id object_getIvar ( id obj, Ivar ivar );
// 設置對象中實例變量的值
void object_setIvar ( id obj, Ivar ivar, id value );


// 獲取類成員變量的信息
Ivar class_getClassVariable ( Class cls, const char *name );
// 添加成員變量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types ); //這個只能夠向在runtime時創(chuàng)建的類添加成員變量
// 獲取整個成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount ); //必須使用free()來釋放這個數(shù)組
⑥ 成員變量列表

objc_class中裳凸,所有的成員變量、屬性的信息是放在鏈表ivars中的劝贸。ivars是一個數(shù)組姨谷,數(shù)組中每個元素是指向Ivar(變量信息)的指針。

  • objc_ivar_list
struct objc_ivar_list {
    int ivar_count                                           OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]                            OBJC2_UNAVAILABLE;
}  

例子映九,獲取所有成員變量

//遍歷獲取Person類所有的成員變量IvarList
- (void) getAllIvarList {
    unsigned int methodCount = 0;
    Ivar * ivars = class_copyIvarList([Person class], &methodCount);
    for (unsigned int i = 0; i < methodCount; i ++) {
        Ivar ivar = ivars[i];
        const char * name = ivar_getName(ivar);
        const char * type = ivar_getTypeEncoding(ivar);
        NSLog(@"Person擁有的成員變量的類型為%s梦湘,名字為 %s ",type, name);
    }
    free(ivars);
}
⑦ 方法

Method 代表類中某個方法的類型

  • Method
/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

objc_method 存儲了方法名,方法類型和方法實現(xiàn):

  • objc_method
struct objc_method {
    SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
    char * _Nullable method_types                            OBJC2_UNAVAILABLE;
    IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

其中件甥,

  • 方法名類型為 SEL
  • 方法類型 method_types 是個 char 指針捌议,存儲方法的參數(shù)類型和返回值類型
  • method_imp 指向了方法的實現(xiàn),本質是一個函數(shù)指針

簡言之引有,Method = SEL + IMP + method_types瓣颅,相當于在SEL和IMP之間建立了一個映射。

操作函數(shù)

// 調用指定方法的實現(xiàn)譬正,返回的是方法實現(xiàn)時的返回宫补,參數(shù)receiver不能為空檬姥,這個比method_getImplementation和method_getName快
id method_invoke ( id receiver, Method m, ... );
// 調用返回一個數(shù)據(jù)結構的方法的實現(xiàn)
void method_invoke_stret ( id receiver, Method m, ... );
// 獲取方法名,希望獲得方法明的C字符串粉怕,使用sel_getName(method_getName(method))
SEL method_getName ( Method m );
// 返回方法的實現(xiàn)
IMP method_getImplementation ( Method m );
// 獲取描述方法參數(shù)和返回值類型的字符串
const char * method_getTypeEncoding ( Method m );
// 獲取方法的返回值類型的字符串
char * method_copyReturnType ( Method m );
// 獲取方法的指定位置參數(shù)的類型字符串
char * method_copyArgumentType ( Method m, unsigned int index );
// 通過引用返回方法的返回值類型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );
// 返回方法的參數(shù)的個數(shù)
unsigned int method_getNumberOfArguments ( Method m );
// 通過引用返回方法指定位置參數(shù)的類型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
// 返回指定方法的方法描述結構體
struct objc_method_description * method_getDescription ( Method m );
// 設置方法的實現(xiàn)
IMP method_setImplementation ( Method m, IMP imp );
// 交換兩個方法的實現(xiàn)
void method_exchangeImplementations ( Method m1, Method m2 );
⑧ 方法列表

方法調用是通過查詢對象的isa指針所指向歸屬類中的methodLists來完成健民。

  • objc_method_list
struct objc_method_list {
    struct objc_method_list * _Nullable 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;
}    

操作函數(shù)

// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types ); //和成員變量不同的是可以為類動態(tài)添加方法。如果有同名會返回NO贫贝,修改的話需要使用method_setImplementation

// 獲取實例方法
Method class_getInstanceMethod ( Class cls, SEL name );

// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );

// 獲取所有方法的數(shù)組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );

// 替代方法的實現(xiàn)
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );

// 返回方法的具體實現(xiàn)
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );

// 類實例是否響應指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );
⑨ 指針:指向方法名與實現(xiàn)
  • SEL
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

在源碼中沒有直接找到 objc_selector 的定義秉犹,從一些書籍上與 Blog 上看到可以將 SEL 理解為一個 char* 指針。

具體這 objc_selector 結構體是什么取決與使用GNU的還是Apple的運行時稚晚, 在Mac OS X中SEL其實被映射為一個C字符串崇堵,可以看作是方法的名字,它并不一個指向具體方法實現(xiàn)(IMP類型才是)蜈彼。

對于所有的類筑辨,只要方法名是相同的,產(chǎn)生的selector都是一樣的幸逆。

操作函數(shù)

// 返回給定選擇器指定的方法的名稱
const char * sel_getName ( SEL sel );
// 在Objective-C Runtime系統(tǒng)中注冊一個方法棍辕,將方法名映射到一個選擇器,并返回這個選擇器
SEL sel_registerName ( const char *str );
// 在Objective-C Runtime系統(tǒng)中注冊一個方法
SEL sel_getUid ( const char *str );
// 比較兩個選擇器
BOOL sel_isEqual ( SEL lhs, SEL rhs );
  • IMP
/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

實際上就是一個函數(shù)指針还绘,指向方法實現(xiàn)的首地址楚昭。通過取得 IMP,我們可以跳過 runtime 的消息傳遞機制拍顷,直接執(zhí)行 IMP指向的函數(shù)實現(xiàn)抚太,這樣省去了 runtime 消息傳遞過程中所做的一系列查找操作,會比直接向對象發(fā)送消息高效一些昔案,當然必須說明的是尿贫,這種方式只適用于極特殊的優(yōu)化場景,如效率敏感的場景下大量循環(huán)的調用某方法踏揣。

操作函數(shù)

// 返回方法的實現(xiàn)
IMP method_getImplementation ( Method m );
// 設置方法的實現(xiàn)
IMP method_setImplementation ( Method m, IMP imp );
// 替代方法的實現(xiàn)
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
⑩ 緩存

上面提到了objc_class結構體中的cache字段庆亡,它用于緩存調用過的方法。這個字段是一個指向objc_cache結構體的指針捞稿,其定義如下:

  • Cache
typedef struct objc_cache *Cache    
  • objc_cache
typedef struct objc_cache *Cache                             OBJC2_UNAVAILABLE;

#define CACHE_BUCKET_NAME(B)  ((B)->method_name)
#define CACHE_BUCKET_IMP(B)   ((B)->method_imp)
#define CACHE_BUCKET_VALID(B) (B)
#ifndef __LP64__
#define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))
#else
#define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>3)) & (mask))
#endif
struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE;
};

該結構體的字段描述如下:

  • mask:一個整數(shù)又谋,指定分配的緩存bucket的總數(shù)。在方法查找過程中娱局,Objective-C runtime使用這個字段來確定開始線性查找數(shù)組的索引位置彰亥。指向方法selector的指針與該字段做一個AND位操作(index = (mask & selector))。這可以作為一個簡單的hash散列算法衰齐。
  • occupied:一個整數(shù)任斋,指定實際占用的緩存bucket的總數(shù)。
  • buckets:指向Method數(shù)據(jù)結構指針的數(shù)組耻涛。這個數(shù)組可能包含不超過mask+1個元素仁卷。需要注意的是穴翩,指針可能是NULL,表示這個緩存bucket沒有被占用锦积,另外被占用的bucket可能是不連續(xù)的芒帕。這個數(shù)組可能會隨著時間而增長。
? 協(xié)議鏈表

前面objc_class的結構體中有個協(xié)議鏈表的參數(shù)丰介,協(xié)議鏈表用來存儲聲明遵守的正式協(xié)議

  • objc_protocol_list
struct objc_protocol_list {
    struct objc_protocol_list * _Nullable next;
    long count;
    __unsafe_unretained Protocol * _Nullable list[1];
};

操作函數(shù)

// 添加協(xié)議
BOOL class_addProtocol ( Class cls, Protocol *protocol );
// 返回類是否實現(xiàn)指定的協(xié)議
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
// 返回類實現(xiàn)的協(xié)議列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );

// 返回指定的協(xié)議
Protocol * objc_getProtocol ( const char *name );
// 獲取運行時所知道的所有協(xié)議的數(shù)組
Protocol ** objc_copyProtocolList ( unsigned int *outCount );
// 創(chuàng)建新的協(xié)議實例
Protocol * objc_allocateProtocol ( const char *name );
// 在運行時中注冊新創(chuàng)建的協(xié)議
void objc_registerProtocol ( Protocol *proto ); //創(chuàng)建一個新協(xié)議后必須使用這個進行注冊這個新協(xié)議背蟆,但是注冊后不能夠再修改和添加新方法。

// 為協(xié)議添加方法
void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );
// 添加一個已注冊的協(xié)議到協(xié)議中
void protocol_addProtocol ( Protocol *proto, Protocol *addition );
// 為協(xié)議添加屬性
void protocol_addProperty ( Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty );
// 返回協(xié)議名
const char * protocol_getName ( Protocol *p );
// 測試兩個協(xié)議是否相等
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );
// 獲取協(xié)議中指定條件的方法的方法描述數(shù)組
struct objc_method_description * protocol_copyMethodDescriptionList ( Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount );
// 獲取協(xié)議中指定方法的方法描述
struct objc_method_description protocol_getMethodDescription ( Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod );
// 獲取協(xié)議中的屬性列表
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );
// 獲取協(xié)議的指定屬性
objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );
// 獲取協(xié)議采用的協(xié)議
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );
// 查看協(xié)議是否采用了另一個協(xié)議
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );
? 分類
  • Category
/// An opaque type that represents a category.
typedef struct objc_category *Category;
  • objc_category
struct objc_category {
    char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
    char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
} 

2. 方法調用流程

objc_msgSend() Tour 系列文章通過對 objc_msgSend 的匯編源碼分析哮幢,總結出以下流程:

2.1 方法調用流程

  1. 檢查 selector 是否需要忽略
  2. 檢查 target 是否為 nil带膀,如果是 nil 就直接 cleanup,然后 return
  3. 在 target 的 Class 中根據(jù) selector 去找 IMP

2.2 尋找 IMP 的過程:

  1. 在當前 class 的方法緩存里尋找(cache methodLists)
  2. 找到了跳到對應的方法實現(xiàn)橙垢,沒找到繼續(xù)往下執(zhí)行
  3. 從當前 class 的 方法列表里查找(methodLists)垛叨,找到了添加到緩存列表里,然后跳轉到對應的方法實現(xiàn)柜某;沒找到繼續(xù)往下執(zhí)行
  4. 從 superClass 的緩存列表和方法列表里查找嗽元,直到找到基類為止
  5. 以上步驟還找不到 IMP,則進入消息動態(tài)處理和消息轉發(fā)流程喂击,詳見這篇文章

我們能在 objc4官方源碼 中找到上述尋找 IMP 的過程剂癌,具體對應的代碼如下:

objc-class.mm

IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    imp = lookUpImpOrNil(cls, sel, nil, 
                         YES/*initialize*/, YES/*cache*/, YES/*resolver*/);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}

objc-runtime-new.mm

IMP lookUpImpOrNil(Class cls, SEL sel, id inst, 
                   bool initialize, bool cache, bool resolver)
{
    IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
    if (imp == _objc_msgForward_impcache) return nil;
    else return imp;
}

等等...

3. 運行時相關的API

3.1 通過 Foundation 框架的 NSObject 類定義的方法

Cocoa 程序中絕大部分類都是 NSObject 類的子類,所以都繼承了 NSObject 的行為翰绊。(NSProxy 類時個例外佩谷,它是個抽象超類)

一些情況下,NSObject 類僅僅定義了完成某件事情的模板监嗜,并沒有提供所需要的代碼谐檀。例如 -description 方法,該方法返回類內(nèi)容的字符串表示裁奇,該方法主要用來調試程序桐猬。NSObject 類并不知道子類的內(nèi)容,所以它只是返回類的名字和對象的地址框喳,NSObject 的子類可以重新實現(xiàn)课幕。

還有一些 NSObject 的方法可以從 Runtime 系統(tǒng)中獲取信息厦坛,允許對象進行自我檢查五垮。例如:

  • -class方法返回對象的類;
  • -isKindOfClass:-isMemberOfClass: 方法檢查對象是否存在于指定的類的繼承體系中(是否是其子類或者父類或者當前類的成員變量)杜秸;
  • -respondsToSelector: 檢查對象能否響應指定的消息放仗;
  • -conformsToProtocol:檢查對象是否實現(xiàn)了指定協(xié)議類的方法;
  • -methodForSelector: 返回指定方法實現(xiàn)的地址撬碟。

常見的一個例子:

//先調用respondsToSelector:來判斷一下
if ([self respondsToSelector:@selector(method)]) {
     [self performSelector:@selector(method)];
}

3.2 runtime的常見API舉例

  • 獲取列表及名字
    unsigned int count;
    //獲取屬性列表
    objc_property_t *propertyList = class_copyPropertyList([self class], &count);
    for (unsigned int i=0; i<count; i++) {
        const char *propertyName = property_getName(propertyList[i]);
        NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
    }
    
    //獲取方法列表
    Method *methodList = class_copyMethodList([self class], &count);
    for (unsigned int i; i<count; i++) {
        Method method = methodList[i];
        NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
    }
    
    //獲取成員變量列表
    Ivar *ivarList = class_copyIvarList([self class], &count);
    for (unsigned int i; i<count; i++) {
        Ivar myIvar = ivarList[i];
        const char *ivarName = ivar_getName(myIvar);
        NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
    }
    
    //獲取協(xié)議列表
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
    for (unsigned int i; i<count; i++) {
        Protocol *myProtocal = protocolList[i];
        const char *protocolName = protocol_getName(myProtocal);
        NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
    }
  • 動態(tài)創(chuàng)建類
Class cls = objc_allocateClassPair(MyClass.class, "MySubClass", 0);
class_addMethod(cls, @selector(submethod1), (IMP)imp_submethod1, "v@:");
class_replaceMethod(cls, @selector(method1), (IMP)imp_submethod1, "v@:");
class_addIvar(cls, "_ivar1", sizeof(NSString *), log(sizeof(NSString *)), "i");

objc_property_attribute_t type = {"T", "@\"NSString\""};
objc_property_attribute_t ownership = { "C", "" };
objc_property_attribute_t backingivar = { "V", "_ivar1"};
objc_property_attribute_t attrs[] = {type, ownership, backingivar};

class_addProperty(cls, "property2", attrs, 3);
objc_registerClassPair(cls);

id instance = [[cls alloc] init];
[instance performSelector:@selector(submethod1)];
[instance performSelector:@selector(method1)];

輸出結果

2014-10-23 11:35:31.006 RuntimeTest[3800:66152] run sub method 1
2014-10-23 11:35:31.006 RuntimeTest[3800:66152] run sub method 1
  • 動態(tài)創(chuàng)建對象
//可以看出class_createInstance和alloc的不同
id theObject = class_createInstance(NSString.class, sizeof(unsigned));
id str1 = [theObject init];
NSLog(@"%@", [str1 class]);
id str2 = [[NSString alloc] initWithString:@"test"];
NSLog(@"%@", [str2 class]);

輸出結果

2014-10-23 12:46:50.781 RuntimeTest[4039:89088] NSString
2014-10-23 12:46:50.781 RuntimeTest[4039:89088] __NSCFConstantString

3.3 runtime的少見API解析

  • idvoid * 轉換API:(__bridge void *)

在 ARC 有效時诞挨,通過 (__bridge void *)轉換 idvoid * 就能夠相互轉換莉撇。為什么轉換?這是因為objc_getAssociatedObject的參數(shù)要求的惶傻。先看一下它的API:

objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)

可以知道棍郎,這個“屬性名”的key是必須是一個void *類型的參數(shù)。所以需要轉換银室。關于這個轉換涂佃,下面給一個轉換的例子:

id obj = [[NSObject alloc] init];

void *p = (__bridge void *)obj;
id o = (__bridge id)p;

關于這個轉換可以了解更多:ARC 類型轉換:顯示轉換 id 和 void *

當然,如果不通過轉換使用這個API蜈敢,就需要這樣使用:

  • 方式1:
objc_getAssociatedObject(self, @"AddClickedEvent");
  • 方式2:
static const void *registerNibArrayKey = &registerNibArrayKey;
NSMutableArray *array = objc_getAssociatedObject(self, registerNibArrayKey);
  • 方式3:
static const char MJErrorKey = '\0';
objc_getAssociatedObject(self, &MJErrorKey);
  • 方式4:
+ (instancetype)cachedPropertyWithProperty:(objc_property_t)property
{
    MJProperty *propertyObj = objc_getAssociatedObject(self, property);
    //省略
}

其中objc_property_t是runtime的類型

typedef struct objc_property *objc_property_t;

4. 運行時實戰(zhàn)指南

上面的API不是提供大家背的辜荠,而是用來查閱的,當你要用到的時候查閱抓狭。因為這些原理和API光看沒用伯病,需要實戰(zhàn)之后再回過頭來查閱和理解。筆者另外寫了runtime的原理與實踐否过。如果想了解runtime的更多知識午笛,可以選擇閱讀這些文章:

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叠纹,一起剝皮案震驚了整個濱河市季研,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌誉察,老刑警劉巖与涡,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異持偏,居然都是意外死亡驼卖,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門鸿秆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酌畜,“玉大人,你說我怎么就攤上這事卿叽∏虐” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵考婴,是天一觀的道長贩虾。 經(jīng)常有香客問我,道長沥阱,這世上最難降的妖魔是什么缎罢? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上策精,老公的妹妹穿的比我還像新娘舰始。我一直安慰自己,他們只是感情好咽袜,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布丸卷。 她就那樣靜靜地躺著,像睡著了一般询刹。 火紅的嫁衣襯著肌膚如雪及老。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天范抓,我揣著相機與錄音骄恶,去河邊找鬼。 笑死匕垫,一個胖子當著我的面吹牛僧鲁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播象泵,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼寞秃,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了偶惠?” 一聲冷哼從身側響起春寿,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎忽孽,沒想到半個月后绑改,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡兄一,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年厘线,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片出革。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡造壮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出骂束,到底是詐尸還是另有隱情耳璧,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布展箱,位于F島的核電站旨枯,受9級特大地震影響,放射性物質發(fā)生泄漏析藕。R本人自食惡果不足惜召廷,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望账胧。 院中可真熱鬧竞慢,春花似錦、人聲如沸治泥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽居夹。三九已至败潦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間准脂,已是汗流浹背劫扒。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留狸膏,地道東北人沟饥。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像湾戳,于是被迫代替她去往敵國和親贤旷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容