本文是參考南峰子Objective-C Runtime系列文章
做一個(gè)自己的總結(jié)难菌,強(qiáng)烈推薦查看原文
1、類型編碼(Type Encoding)
在定義一個(gè)方法如下:第四個(gè)參數(shù)就是參考Type Encoding
class_addMethod([self class],@selector(eat:),(IMP) eatIMP, "v@:@");
v@:@
: 沒(méi)有返回值遇绞,參數(shù)類型依次為,id
猎塞、SEL
试读、id
定義一個(gè)屬性如下:就是參考Property Type String
官方文檔寫了杠纵,T
為開(kāi)始荠耽,V
為結(jié)束,中間參考表
The string starts with a
T
followed by the@encode
type and a comma, and finishes with aV
followed by the name of the backing instance variable. Between these, the attributes are specified by the following descriptors, separated by commas:
objc_property_attribute_t type = { "T", "@\"NSString\"" };//類型
objc_property_attribute_t ownership = { "C", "" }; // C = copy
objc_property_attribute_t nonatomic = { "N", "" }; //nonatomic
objc_property_attribute_t backingivar = { "V", "_property1" };//V 實(shí)例變量
objc_property_attribute_t attrs[] = { type, ownership,nonatomic, backingivar };
class_addProperty(cls, "property1", attrs, 4);
2比藻、Ivar
Ivar
表示實(shí)例成員變量類型铝量,其實(shí)際是指向objc_ivar
結(jié)構(gòu)體的指針,其定義如下:
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char * _Nullable ivar_name // 變量名 OBJC2_UNAVAILABLE;
char * _Nullable ivar_type //變量類型 OBJC2_UNAVAILABLE;
int ivar_offset // 基地址偏移字節(jié) OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
成員變量操作函數(shù)
// 獲取成員變量名
const char * ivar_getName ( Ivar v );
// 獲取成員變量類型編碼
const char * ivar_getTypeEncoding ( Ivar v );
// 獲取成員變量的偏移量
ptrdiff_t ivar_getOffset ( Ivar v );
-
ivar_getOffset
: 對(duì)于類型id
或其它對(duì)象類型的實(shí)例變量银亲,可以調(diào)用id object_getIvar(id _Nullable obj, Ivar _Nonnull ivar)
和void object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value)
來(lái)直接訪問(wèn)成員變量慢叨,而不使用偏移量
3、objc_property_t
objc_property_t
是表示屬性類型务蝠,其實(shí)際是指向objc_property
結(jié)構(gòu)體的指針拍谐,其定義如下:
typedef struct objc_property *objc_property_t;
3.1、objc_property_attribute_t
objc_property_attribute_t
定義了屬性的特性(attribute
)馏段,它是一個(gè)結(jié)構(gòu)體轩拨,定義如下:
typedef struct {
const char *name; // 特性名
const char *value; // 特性值
} objc_property_attribute_t;
屬性操作函數(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 );
注意:property_copyAttributeValue
和 property_copyAttributeList
使用完后需要調(diào)用free()釋放
4、關(guān)聯(lián)對(duì)象(Associated Object)
關(guān)聯(lián)對(duì)象類似于成員變量院喜,不過(guò)是在運(yùn)行時(shí)添加的亡蓉。我們通常會(huì)把成員變量(Ivar
)放在類聲明的頭文件中,或者放在類實(shí)現(xiàn)的@implementation
后面喷舀。但這有一個(gè)缺點(diǎn)砍濒,我們不能在分類中添加成員變量。如果我們嘗試在分類中添加新的成員變量硫麻,編譯器會(huì)報(bào)錯(cuò)爸邢。
我們可能希望通過(guò)使用(甚至是濫用)全局變量來(lái)解決這個(gè)問(wèn)題。但這些都不是Ivar
拿愧,因?yàn)樗麄儾粫?huì)連接到一個(gè)單獨(dú)的實(shí)例甲棍。
Objective-C
針對(duì)這一問(wèn)題,提供了一個(gè)解決方案:即關(guān)聯(lián)對(duì)象(Associated Object
)
關(guān)聯(lián)對(duì)象操作函數(shù)如下:
// 設(shè)置關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject ( id object, const void *key, id value, objc_AssociationPolicy policy );
// 獲取關(guān)聯(lián)對(duì)象
id objc_getAssociatedObject ( id object, const void *key );
// 移除關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects ( id object );
objc_AssociationPolicy
如下:
BJC_ASSOCIATION_ASSIGN
OBJC_ASSOCIATION_RETAIN_NONATOMIC
OBJC_ASSOCIATION_COPY_NONATOMIC
OBJC_ASSOCIATION_RETAIN
OBJC_ASSOCIATION_COPY
當(dāng)宿主對(duì)象被釋放時(shí)赶掖,會(huì)根據(jù)指定的內(nèi)存管理策略來(lái)處理關(guān)聯(lián)對(duì)象感猛。
如果指定的策略是assign
,則宿主釋放時(shí)奢赂,關(guān)聯(lián)對(duì)象不會(huì)被釋放陪白;
如果指定的是retain
、copy
膳灶,則宿主釋放時(shí)咱士,關(guān)聯(lián)對(duì)象會(huì)被釋放
5立由、Method
typedef struct objc_method *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_name
: 方法名 -
method_imp
: 方法實(shí)現(xiàn)
objc_method
結(jié)構(gòu)體中包含了一個(gè)SEL
和IMP
,實(shí)際相當(dāng)于在SEL
和IMP
之間做了一個(gè)映射,有了SEL
序厉,我們便能找到對(duì)應(yīng)的IMP
objc_msgSend
有兩個(gè)隱藏參數(shù):
- 消息接收對(duì)象
- 方法的
selector
這兩個(gè)參數(shù)為方法的實(shí)現(xiàn)提供了調(diào)用者的信息锐膜。之所以說(shuō)是隱藏的,是因?yàn)樗鼈冊(cè)诙x方法的源代碼中沒(méi)有聲明弛房。它們是在編譯期被插入實(shí)現(xiàn)代碼的道盏。
雖然這些參數(shù)沒(méi)有顯示聲明,但在代碼中仍然可以引用它們文捶。我們可以使用self
來(lái)引用接收者對(duì)象荷逞,使用_cmd
來(lái)引用選擇器。
5.1粹排、SEL(方法名)
SEL
又叫選擇器种远,是表示一個(gè)方法的selector
的指針,其定義如下:
typedef struct objc_selector *SEL;
Objective-C
在編譯時(shí)顽耳,會(huì)根據(jù)每個(gè)方法的名字坠敷、參數(shù)、生成一個(gè)唯一的標(biāo)識(shí)(Int
類型地址)射富,這個(gè)標(biāo)識(shí)就是SEL
膝迎,根據(jù)一個(gè)方法名hash
化了的key
值,能唯一代表一個(gè)方法辉浦,它的存在只是為了加快了方法的查詢速度
我們可以在運(yùn)行時(shí)添加新的selector
弄抬,也可以在運(yùn)行時(shí)獲取已存在的selector
,我們可以通過(guò)下面三種方法來(lái)獲取SEL
:
-
sel_registerName
函數(shù) -
Objective-C
編譯器提供的@selector()
-
NSSelectorFromString()
方法
選擇器相關(guān)操作如下:
// 返回給定選擇器指定的方法的名稱
const char * sel_getName ( SEL sel );
// 在Objective-C Runtime系統(tǒng)中注冊(cè)一個(gè)方法宪郊,將方法名映射到一個(gè)選擇器掂恕,并返回這個(gè)選擇器
SEL sel_registerName ( const char *str );
// 在Objective-C Runtime系統(tǒng)中注冊(cè)一個(gè)方法
SEL sel_getUid ( const char *str );
// 比較兩個(gè)選擇器
BOOL sel_isEqual ( SEL lhs, SEL rhs );
1.sel_registerName
:在我們將一個(gè)方法添加到類定義時(shí),我們必須在Objective-C Runtime
系統(tǒng)中注冊(cè)一個(gè)方法名以獲取方法的選擇器
5.2弛槐、IMP(方法實(shí)現(xiàn))
IMP
實(shí)際上是一個(gè)函數(shù)指針懊亡,指向方法實(shí)現(xiàn)的首地址,其定義如下:
typedef void (*IMP)(void /* id, SEL, ... */ );
第一個(gè)參數(shù)是指向self
的指針 (如果是對(duì)象方法乎串,則指向?qū)ο笏鶎兕惖暝妫绻穷惙椒ǎ瑒t指向元類)
第二參數(shù)是方法選擇器selector
第三個(gè)參數(shù)起叹誉,就是方法參數(shù)
方法名SEL
的查找就是為了查找方法的實(shí)現(xiàn)IMP
鸯两。由于每個(gè)方法對(duì)應(yīng)唯一的SEL
,因此我們可以通過(guò)SEL
快速準(zhǔn)確地獲取它所以對(duì)應(yīng)的IMP
长豁。獲取IMP
后钧唐,我們就獲得了執(zhí)行這個(gè)方法代碼的入口點(diǎn),就可以像調(diào)用普通C函數(shù)
來(lái)使用這個(gè)函數(shù)了匠襟。
通過(guò)取得IMP
钝侠,我們可以跳過(guò)Runtime
的消息傳遞機(jī)制该园,直接執(zhí)行IMP
指向的函數(shù)實(shí)現(xiàn),這樣省去了Runtime
消息傳遞過(guò)程中所做的一系列查找操作帅韧,會(huì)比直接向?qū)ο蟀l(fā)送消息高效一些里初。
5.2.1、objc_method_description
方法描述包括:方法名和方法類型(類方法忽舟、實(shí)例方法)
struct objc_method_description {
SEL _Nullable name; /**< The name of the method */
char * _Nullable types; /**< The types of the method arguments */
};
方法操作相關(guān)函數(shù)如下:
// 調(diào)用指定方法的實(shí)現(xiàn)
id method_invoke ( id receiver, Method m, ... );
// 調(diào)用返回一個(gè)數(shù)據(jù)結(jié)構(gòu)的方法的實(shí)現(xiàn)
void method_invoke_stret ( id receiver, Method m, ... );
// 獲取方法名
SEL method_getName ( Method m );
// 返回方法的實(shí)現(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 );
// 通過(guò)引用返回方法的返回值類型字符串
void method_getReturnType ( Method m, char *dst, size_t dst_len );
// 返回方法的參數(shù)的個(gè)數(shù)
unsigned int method_getNumberOfArguments ( Method m );
// 通過(guò)引用返回方法指定位置參數(shù)的類型字符串
void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
// 返回指定方法的方法描述結(jié)構(gòu)體
struct objc_method_description * method_getDescription ( Method m );
// 設(shè)置方法的實(shí)現(xiàn)
IMP method_setImplementation ( Method m, IMP imp );
// 交換兩個(gè)方法的實(shí)現(xiàn)
void method_exchangeImplementations ( Method m1, Method m2 );
-
method_invoke
: 返回的是實(shí)際實(shí)現(xiàn)的返回值双妨。參數(shù)receiver
不能為空。這個(gè)方法的效率會(huì)比method_getImplementation
和method_getName
更快 -
method_getName
: 返回的是一個(gè)SEL
萧诫。如果想獲取方法名的C
字符串斥难,可以使用sel_getName(method_getName(method))
枝嘶。 -
method_getReturnType
: 類型字符串會(huì)被拷貝到dst
中 -
method_setImplementation
: 注意該函數(shù)返回值是方法之前的實(shí)現(xiàn)
6帘饶、消息
給一個(gè)對(duì)象發(fā)送消息:如[person run];
會(huì)被編譯成objc_msgSend(person, @selector(run));
objc_msgSend
具體查找流程如下
- 通過(guò)
isa
指針找到對(duì)象所屬的類 - 查找類的
cache
列表,成功群扶,跳轉(zhuǎn)到第4步及刻,失敗,跳轉(zhuǎn)到第3步 - 查找類的
methodLists
列表 - 查找名字相同的方法竞阐,成功跳轉(zhuǎn)到代碼實(shí)現(xiàn)缴饭,失敗,跳轉(zhuǎn)到第5步
- 沿著集成體系向上查找骆莹,成功跳轉(zhuǎn)到代碼實(shí)現(xiàn)颗搂,失敗,消息轉(zhuǎn)發(fā)
6.1幕垦、消息轉(zhuǎn)發(fā)
- 動(dòng)態(tài)方法解析:詢問(wèn)是否有動(dòng)態(tài)方法來(lái)處理這個(gè)
未知消息
丢氢,有則消息轉(zhuǎn)發(fā)結(jié)束 - 備用接收者:詢問(wèn)其他接收者是否能處理這條消息,有先改,則將這條消息轉(zhuǎn)發(fā)給該接收者疚察,消息轉(zhuǎn)發(fā)結(jié)束
- 完整轉(zhuǎn)發(fā):將這個(gè)
未知消息
所有細(xì)節(jié)封裝到NSInvocation
對(duì)象中,再次詢問(wèn)仇奶,如果此時(shí)還未能處理貌嫡,則消息處理失敗
+ (BOOL)resolveInstanceMethod:(SEL)sel{}
- (id)forwardingTargetForSelector:(SEL)aSelector{}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{}
- (void)forwardInvocation:(NSInvocation *)anInvocation{}
7、Protocol
Protocol
定義如下该溯,可以看出Protocol
就是一個(gè)對(duì)象結(jié)構(gòu)體
typedef struct objc_object Protocol;
Protocol相關(guān)操作函數(shù)如下:
// 返回指定的協(xié)議
Protocol * objc_getProtocol ( const char *name );
// 獲取運(yùn)行時(shí)所知道的所有協(xié)議的數(shù)組
Protocol ** objc_copyProtocolList ( unsigned int *outCount );
// 創(chuàng)建新的協(xié)議實(shí)例
Protocol * objc_allocateProtocol ( const char *name );
// 在運(yùn)行時(shí)中注冊(cè)新創(chuàng)建的協(xié)議
void objc_registerProtocol ( Protocol *proto );
// 為協(xié)議添加方法
void protocol_addMethodDescription ( Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod );
// 添加一個(gè)已注冊(cè)的協(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 );
// 測(cè)試兩個(gè)協(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é)議是否采用了另一個(gè)協(xié)議
BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );
-
objc_getProtocol
: 如果僅僅是聲明了一個(gè)協(xié)議岛抄,而未在任何類中實(shí)現(xiàn)這個(gè)協(xié)議,則該函數(shù)返回的是nil狈茉。 -
objc_copyProtocolList
: 獲取到的數(shù)組需要使用free()
來(lái)釋放 -
objc_allocateProtocol
: 如果同名的協(xié)議已經(jīng)存在夫椭,則返回nil -
objc_registerProtocol
: 創(chuàng)建一個(gè)新的協(xié)議后,必須調(diào)用該函數(shù)以在運(yùn)行時(shí)中注冊(cè)新的協(xié)議论皆。協(xié)議注冊(cè)后便可以使用益楼,但不能再做修改猾漫,即注冊(cè)完后不能再向協(xié)議添加方法或協(xié)議
注意: 協(xié)議一旦注冊(cè)后就不可再修改,即無(wú)法再通過(guò)調(diào)用protocol_addMethodDescription
感凤、protocol_addProtocol
和protocol_addProperty
往協(xié)議中添加方法屬性等
8悯周、 Category
Category定義如下,表示指向分類結(jié)構(gòu)體的指針
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; // 實(shí)例方法列表
struct objc_method_list *class_methods OBJC2_UNAVAILABLE; // 類方法列表
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 分類所實(shí)現(xiàn)的協(xié)議列表
}
runtime
并沒(méi)有提供過(guò)多的函數(shù)來(lái)處理分類
9陪竿、id
typedef struct objc_object *id;
它是一個(gè)objc_object
結(jié)構(gòu)類型的指針禽翼。它的存在可以讓我們實(shí)現(xiàn)類似于C++
中泛型的一些操作。該類型的對(duì)象可以轉(zhuǎn)換為任何一種對(duì)象族跛,有點(diǎn)類似于C語(yǔ)言
中void *
指針類型的作用闰挡。