摘要:這篇文章首先介紹runtime原理,包括類,超類,元類,super_class,isa,對象,方法,SEL,IMP等概念,同時分別介紹與這些概念有關的API。接着介紹方法調用流程,以及尋找IMP的過程。而後,介紹一下這些API的常見用法,並介紹runtime的冷門知識。最後介紹一下runtime的實戰指南。git
Tips:蘋果公開的源代碼在這裏能夠查,opensource.apple.com/tarballs/程序員
例如,其中,有兩個比較常見須要學習源碼的地址:github
固然,若是你想在github上在線查看源代碼,能夠點這裏:runtime,runloop算法
Runtime 又叫運行時,是一套底層的 C 語言 API,其爲 iOS 內部的核心之一,咱們平時編寫的 OC 代碼,底層都是基於它來實現的。好比:數組
// 發送消息
[receiver message];
複製代碼
// 底層運行時會被編譯器轉化爲:
objc_msgSend(receiver, selector)
複製代碼
// 若是其還有參數好比:
[receiver message:(id)arg...];
複製代碼
// 底層運行時會被編譯器轉化爲:
objc_msgSend(receiver, selector, arg1, arg2, ...)
複製代碼
以上你可能看不出它的價值,可是咱們須要瞭解的是 Objective-C 是一門動態語言,它會將一些工做放在代碼運行時才處理而並不是編譯時。也就是說,有不少類和成員變量在咱們編譯的時是不知道的,而在運行時,咱們所編寫的代碼會轉換成完整的肯定的代碼運行。緩存
所以,編譯器是不夠的,咱們還須要一個運行時系統(Runtime system)來處理編譯後的代碼。Runtime 基本是用 C 和彙編寫的,因而可知蘋果爲了動態系統的高效而作出的努力。蘋果和 GNU 各自維護一個開源的 Runtime 版本,這兩個版本之間都在努力保持一致。bash
Objc 在三種層面上與 Runtime 系統進行交互:markdown
在 Objective-C 中,類、對象和方法都是一個 C 的結構體,從 objc/objc.h
(對象,objc_object
,id
)以及objc/runtime.h
(其它,類,方法,方法列表,變量列表,屬性列表等相關的)以及中,咱們能夠找到他們的定義。數據結構
類對象(Class)是由程序員定義並在運行時由編譯器建立的,它沒有本身的實例變量,這裏須要注意的是類的成員變量和實例方法列表是屬於實例對象的,但其存儲於類對象當中的。咱們在objc/objc.h
下看看Class的定義:app
/// An opaque type that represents an Objective-C class. typedef struct objc_class *Class; 複製代碼
能夠看到類是由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 *` */ 複製代碼
參數解析
cache:用於緩存最近使用的方法。一個接收者對象接收到一個消息時,它會根據isa
指針去查找可以響應這個消息的對象。在實際使用中,這個對象只有一部分方法是經常使用的,不少方法其實不多用或者根本用不上。這種狀況下,若是每次消息來時,咱們都是methodLists
中遍歷一遍,性能勢必不好。這時,cache就派上用場了。在咱們每次調用過一個方法後,這個方法就會被緩存到cache列表中,下次調用的時候runtime就會優先去cache中查找,若是cache沒有,纔去methodLists
中查找方法。這樣,對於那些常常用到的方法的調用,但提升了調用的效率。
version:咱們可使用這個字段來提供類的版本信息。這對於對象的序列化很是有用,它但是讓咱們識別出不一樣類定義版本中實例變量佈局的改變。
protocols:固然能夠看出這一個objc_protocol_list
的指針。關於objc_protocol_list
的結構體構成後面會講。
獲取類的類名
// 獲取類的類名
const char * class_getName ( Class cls );
複製代碼
動態建立類
// 建立一個新類和元類
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes ); //若是建立的是root class,則superclass爲Nil。extraBytes一般爲0
// 銷燬一個類及其相關聯的類
void objc_disposeClassPair ( Class cls ); //在運行中還存在或存在子類實例,就不可以調用這個。
// 在應用中註冊由objc_allocateClassPair建立的類
void objc_registerClassPair ( Class cls ); //建立了新類後,而後使用class_addMethod,class_addIvar函數爲新類添加方法,實例變量和屬性後再調用這個來註冊類,再以後就可以用了。
複製代碼
實例對象是咱們對類對象alloc
或者new
操做時所建立的,在這個過程當中會拷貝實例所屬的類的成員變量,但並不拷貝類定義的方法。調用實例方法時,系統會根據實例的isa
指針去類的方法列表及父類的方法列表中尋找與消息對應的selector
指向的方法。一樣的,咱們也來看下其定義:
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
複製代碼
能夠看到,這個結構體只有一個isa
變量,指向實例對象所屬的類。任何帶有以指針開始並指向類結構的結構均可以被視做objc_object
, 對象最重要的特色是能夠給其發送消息。 NSObject類的alloc
和allocWithZone:
方法使用函數class_createInstance
來建立objc_object
數據結構。
另外咱們常見的id
類型,它是一個objc_object
結構類型的指針。該類型的對象能夠轉換爲任何一種對象,相似於C語言中void *
指針類型的做用。其定義以下所示:
/// 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 );
// 建立並返回一個指向全部已註冊類的指針列表
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 );
複製代碼
動態建立對象
// 建立類實例
id class_createInstance ( Class cls, size_t extraBytes ); //會在heap裏給類分配內存。這個方法和+alloc方法相似。
// 在指定位置建立類實例
id objc_constructInstance ( Class cls, void *bytes );
// 銷燬類實例
void * objc_destructInstance ( id obj ); //不會釋放移除任何相關引用
複製代碼
元類(Metaclass)就是類對象的類,每一個類都有本身的元類,也就是objc_class
結構體裏面isa
指針所指向的類. Objective-C的類方法是使用元類的根本緣由,由於其中存儲着對應的類對象調用的方法即類方法。
當向對象發消息,runtime會在這個對象所屬類方法列表中查找發送消息對應的方法,但當向類發送消息時,runtime就會在這個類的meta class方法列表裏查找。全部的meta class,包括Root class,Superclass,Subclass的isa都指向Root class的meta class,這樣可以造成一個閉環。
因此由上圖能夠看到,在給實例對象或類對象發送消息時,尋找方法列表的規則爲:
元類,就像以前的類同樣,它也是一個對象,也能夠調用它的方法。因此這就意味着它必須也有一個類。全部的元類都使用根元類做爲他們的類。好比全部NSObject的子類的元類都會以NSObject的元類做爲他們的類。
根據這個規則,全部的元類使用根元類做爲他們的類,根元類的元類則就是它本身。也就是說基類的元類的isa指針指向他本身。
操做函數
// 獲取類的父類
Class class_getSuperclass ( Class cls );
// 判斷給定的Class是不是一個meta class
BOOL class_isMetaClass ( Class cls );
複製代碼
// 獲取實例大小
size_t class_getInstanceSize ( Class cls );
複製代碼
在Objective-C中,屬性(property)和成員變量是不一樣的。那麼,屬性的本質是什麼?它和成員變量之間有什麼區別?簡單來講屬性是添加了存取方法的成員變量,也就是:
@property = ivar + getter + setter;
所以,咱們每定義一個@property都會添加對應的ivar, getter和setter到類結構體objc_class
中。具體來講,系統會在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); } } 複製代碼
/// 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
結構體包含name
和value
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;
複製代碼
經常使用的屬性以下:
例如
@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); } 複製代碼
操做函數
// 獲取屬性名
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
結構體的指針
/// An opaque type that represents an instance variable. typedef struct objc_ivar *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
。它表示基地址偏移字節。
操做函數
//成員變量操做函數
// 修改類實例的實例變量的值
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
// 獲取對象實例變量的值
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
// 返回指向給定對象分配的任何額外字節的指針
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時建立的類添加成員變量
// 獲取整個成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount ); //必須使用free()來釋放這個數組
複製代碼
在objc_class
中,全部的成員變量、屬性的信息是放在鏈表ivars
中的。ivars
是一個數組,數組中每一個元素是指向Ivar
(變量信息)的指針。
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 表明類中某個方法的類型
/// An opaque type that represents a method in a class definition. typedef struct objc_method *Method; 複製代碼
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;
複製代碼
其中,
簡言之,Method = SEL + IMP + method_types,至關於在SEL和IMP之間創建了一個映射。
操做函數
// 調用指定方法的實現,返回的是方法實現時的返回,參數receiver不能爲空,這個比method_getImplementation和method_getName快
id method_invoke ( id receiver, Method m, ... );
// 調用返回一個數據結構的方法的實現
void method_invoke_stret ( id receiver, Method m, ... );
// 獲取方法名,但願得到方法明的C字符串,使用sel_getName(method_getName(method))
SEL method_getName ( Method m );
// 返回方法的實現
IMP method_getImplementation ( Method m );
// 獲取描述方法參數和返回值類型的字符串
const char * method_getTypeEncoding ( Method m );
// 獲取方法的返回值類型的字符串
char * method_copyReturnType ( Method m );
// 獲取方法的指定位置參數的類型字符串
char * method_copyArgumentType ( Method m, unsigned int index );
// 經過引用返回方法的返回值類型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );
// 返回方法的參數的個數
unsigned int method_getNumberOfArguments ( Method m );
// 經過引用返回方法指定位置參數的類型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
// 返回指定方法的方法描述結構體
struct objc_method_description * method_getDescription ( Method m );
// 設置方法的實現
IMP method_setImplementation ( Method m, IMP imp );
// 交換兩個方法的實現
void method_exchangeImplementations ( Method m1, Method m2 );
複製代碼
方法調用是經過查詢對象的isa指針所指向歸屬類中的methodLists來完成。
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; } 複製代碼
操做函數
// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types ); //和成員變量不一樣的是能夠爲類動態添加方法。若是有同名會返回NO,修改的話須要使用method_setImplementation
// 獲取實例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
// 獲取全部方法的數組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
// 替代方法的實現
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
// 返回方法的具體實現
IMP class_getMethodImplementation ( Class cls, SEL name );
IMP class_getMethodImplementation_stret ( Class cls, SEL name );
// 類實例是否響應指定的selector
BOOL class_respondsToSelector ( Class cls, SEL 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字符串,能夠看做是方法的名字,它並不一個指向具體方法實現(IMP類型纔是)。
對於全部的類,只要方法名是相同的,產生的selector都是同樣的。
操做函數
// 返回給定選擇器指定的方法的名稱
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 );
複製代碼
/// 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 複製代碼
實際上就是一個函數指針,指向方法實現的首地址。經過取得 IMP,咱們能夠跳過 runtime 的消息傳遞機制,直接執行 IMP指向的函數實現,這樣省去了 runtime 消息傳遞過程當中所作的一系列查找操做,會比直接向對象發送消息高效一些,固然必須說明的是,這種方式只適用於極特殊的優化場景,如效率敏感的場景下大量循環的調用某方法。
操做函數
// 返回方法的實現
IMP method_getImplementation ( Method m );
// 設置方法的實現
IMP method_setImplementation ( Method m, IMP imp );
// 替代方法的實現
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
複製代碼
上面提到了objc_class結構體中的cache字段,它用於緩存調用過的方法。這個字段是一個指向objc_cache結構體的指針,其定義以下:
typedef struct objc_cache *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; }; 複製代碼
該結構體的字段描述以下:
前面objc_class
的結構體中有個協議鏈表的參數,協議鏈表用來存儲聲明遵照的正式協議
struct objc_protocol_list {
struct objc_protocol_list * _Nullable next;
long count;
__unsafe_unretained Protocol * _Nullable list[1];
};
複製代碼
操做函數
// 添加協議
BOOL class_addProtocol ( Class cls, Protocol *protocol );
// 返回類是否實現指定的協議
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
// 返回類實現的協議列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
// 返回指定的協議
Protocol * objc_getProtocol ( const char *name );
// 獲取運行時所知道的全部協議的數組
Protocol ** objc_copyProtocolList ( unsigned int *outCount );
// 建立新的協議實例
Protocol * objc_allocateProtocol ( const char *name );
// 在運行時中註冊新建立的協議
void objc_registerProtocol ( Protocol *proto ); //建立一個新協議後必須使用這個進行註冊這個新協議,可是註冊後不可以再修改和添加新方法。
// 爲協議添加方法
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 );
// 返回協議名
const char * protocol_getName ( Protocol *p );
// 測試兩個協議是否相等
BOOL protocol_isEqual ( Protocol *proto, Protocol *other );
// 獲取協議中指定條件的方法的方法描述數組
struct objc_method_description * protocol_copyMethodDescriptionList ( Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount );
// 獲取協議中指定方法的方法描述
struct objc_method_description protocol_getMethodDescription ( Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod );
// 獲取協議中的屬性列表
objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );
// 獲取協議的指定屬性
objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );
// 獲取協議採用的協議
Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );
// 查看協議是否採用了另外一個協議
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );
複製代碼
/// An opaque type that represents a category. typedef struct objc_category *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;
}
複製代碼
objc_msgSend() Tour 系列文章經過對 objc_msgSend
的彙編源碼分析,總結出如下流程:
咱們能在 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; } 複製代碼
等等...
Cocoa 程序中絕大部分類都是 NSObject 類的子類,因此都繼承了 NSObject 的行爲。(NSProxy 類時個例外,它是個抽象超類)
一些狀況下,NSObject 類僅僅定義了完成某件事情的模板,並無提供所須要的代碼。例如 -description 方法,該方法返回類內容的字符串表示,該方法主要用來調試程序。NSObject 類並不知道子類的內容,因此它只是返回類的名字和對象的地址,NSObject 的子類能夠從新實現。
還有一些 NSObject 的方法能夠從 Runtime 系統中獲取信息,容許對象進行自我檢查。例如:
-class
方法返回對象的類;-isKindOfClass:
和 -isMemberOfClass:
方法檢查對象是否存在於指定的類的繼承體系中(是不是其子類或者父類或者當前類的成員變量);-respondsToSelector:
檢查對象可否響應指定的消息;-conformsToProtocol:
檢查對象是否實現了指定協議類的方法;-methodForSelector:
返回指定方法實現的地址。常見的一個例子:
//先調用respondsToSelector:來判斷一下 if ([self respondsToSelector:@selector(method)]) { [self performSelector:@selector(method)]; } 複製代碼
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]); } //獲取協議列表 __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]); } 複製代碼
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
複製代碼
//能夠看出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
複製代碼
id
和 void *
轉換API:(__bridge void *)
在 ARC 有效時,經過 (__bridge void *)
轉換 id
和 void *
就可以相互轉換。爲何轉換?這是由於objc_getAssociatedObject
的參數要求的。先看一下它的API:
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
複製代碼
能夠知道,這個「屬性名」的key是必須是一個void *
類型的參數。因此須要轉換。關於這個轉換,下面給一個轉換的例子:
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
複製代碼
關於這個轉換能夠了解更多:ARC 類型轉換:顯示轉換 id 和 void *
固然,若是不經過轉換使用這個API,就須要這樣使用:
objc_getAssociatedObject(self, @"AddClickedEvent"); 複製代碼
static const void *registerNibArrayKey = ®isterNibArrayKey;
複製代碼
NSMutableArray *array = objc_getAssociatedObject(self, registerNibArrayKey);
複製代碼
static const char MJErrorKey = '\0'; 複製代碼
objc_getAssociatedObject(self, &MJErrorKey);
複製代碼
+ (instancetype)cachedPropertyWithProperty:(objc_property_t)property
{
MJProperty *propertyObj = objc_getAssociatedObject(self, property);
//省略
}
複製代碼
其中objc_property_t
是runtime的類型
typedef struct objc_property *objc_property_t;
複製代碼
上面的API不是提供你們背的,而是用來查閱的,當你要用到的時候查閱。由於這些原理和API光看沒用,須要實戰以後再回過頭來查閱和理解。筆者另外寫了runtime的原理與實踐。若是想了解runtime的更多知識,能夠選擇閱讀這些文章: