什么是runtime
官方描述:
The Objective-C language defers as many decisions as it can from compile time and link time to runtime.
“盡量將決定放到運(yùn)行的時(shí)候唬渗,而不是在編譯和鏈接過(guò)程”
runtime是一個(gè)C語(yǔ)言庫(kù)饮戳,包含了很多底層的純C語(yǔ)言API。 平時(shí)編寫的OC代碼中鹅颊,程序運(yùn)行譬淳,其實(shí)最終都是轉(zhuǎn)成了runtime的C語(yǔ)言代碼揩页,runtime算是OC的幕后工作者 茄蚯。
- 特點(diǎn)
OC與其他語(yǔ)言不同的一點(diǎn)就是镜硕,函數(shù)調(diào)用采用了消息轉(zhuǎn)發(fā)
的機(jī)制茂浮,但直到程序運(yùn)行之前双谆,消息都沒(méi)有與任何方法綁定起來(lái)。只有在真正運(yùn)行的時(shí)候席揽,才會(huì)根據(jù)函數(shù)的名字來(lái)顽馋,確定該調(diào)用的函數(shù)。
runtime 是有個(gè)兩個(gè)版本的:
在Objective-C 1.0
使用的是legacy幌羞,在2.0
使用的是modern寸谜。
現(xiàn)在一般來(lái)說(shuō)runtime都是指modern。
1. isa指針
首先要了解它底層的一些常用數(shù)據(jù)結(jié)構(gòu)属桦,比如isa指針熊痴。
當(dāng)創(chuàng)建一個(gè)新對(duì)象時(shí),會(huì)為它分配一段內(nèi)存聂宾,該對(duì)象的實(shí)例變量也會(huì)被初始化果善。第一個(gè)變量就是一個(gè)指向它的類的指針(isa)。
通過(guò)isa指針系谐,一個(gè)對(duì)象可以訪問(wèn)它的類巾陕,并通過(guò)它的類來(lái)訪問(wèn)所有父類讨跟。
- 一個(gè)實(shí)例對(duì)象,在runtime中用結(jié)構(gòu)體表示
// 描述類中的一個(gè)方法
typedef struct objc_method *Method;
// 實(shí)例變量
typedef struct objc_ivar *Ivar;
// 類別Category
typedef struct objc_category *Category;
// 類中聲明的屬性
typedef struct objc_property *objc_property_t;
查看runtime源碼可以看到關(guān)于isa結(jié)構(gòu)鄙煤。
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
struct {
ISA_BITFIELD; // defined in isa.h
};
};
下面的代碼對(duì)isa_t
中的結(jié)構(gòu)體進(jìn)行了位域聲明晾匠,地址從nonpointer
起到extra_rc
結(jié)束,從低到高進(jìn)行排列梯刚。位域也是對(duì)結(jié)構(gòu)體內(nèi)存布局進(jìn)行了一個(gè)聲明凉馆,通過(guò)下面的結(jié)構(gòu)體成員變量可以直接操作某個(gè)地址。位域總共占8字節(jié)亡资,所有的位域加在一起正好是64位澜共。
小提示:union
中bits
可以操作整個(gè)內(nèi)存區(qū),而位域只能操作對(duì)應(yīng)的位沟于。
define ISA_BITFIELD \
uintptr_t nonpointer : 1; //指針是否優(yōu)化過(guò) \
uintptr_t has_assoc : 1; //是否有設(shè)置過(guò)關(guān)聯(lián)對(duì)象咳胃,如果沒(méi)有植康,釋放時(shí)會(huì)更快 \
uintptr_t has_cxx_dtor : 1; //是否有C++的析構(gòu)函數(shù)(.cxx_destruct)旷太,如果沒(méi)有,釋放時(shí)會(huì)更快 \
uintptr_t shiftcls : 33; //存儲(chǔ)著Class销睁、Meta-Class對(duì)象的內(nèi)存地址信息 \
uintptr_t magic : 6; //用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化 \
uintptr_t weakly_referenced : 1; //是否有被弱引用指向過(guò)供璧,如果沒(méi)有,釋放時(shí)會(huì)更快 \
uintptr_t deallocating : 1; //對(duì)象是否正在釋放 \
uintptr_t has_sidetable_rc : 1; //引用計(jì)數(shù)器是否過(guò)大無(wú)法存儲(chǔ)在isa中 \
uintptr_t extra_rc : 19 //里面存儲(chǔ)的值是引用計(jì)數(shù)器減1
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
- nonpointer
0:代表普通的指針冻记,存儲(chǔ)著Class睡毒、Meta-Class
對(duì)象的內(nèi)存地址。
1:代表優(yōu)化過(guò)冗栗,使用位域存儲(chǔ)更多的信息演顾。- has_assoc
是否有設(shè)置過(guò)關(guān)聯(lián)對(duì)象。如果沒(méi)有隅居,釋放時(shí)會(huì)更快钠至。- has_cxx_dtor
是否有C++的析構(gòu)函數(shù).cxx_destruct
如果沒(méi)有,釋放時(shí)會(huì)更快胎源。- shiftcls
存儲(chǔ)著Class棉钧、Meta-Class
對(duì)象的內(nèi)存地址信息- magic
用于在調(diào)試時(shí),分辨對(duì)象是否未完成初始化- weakly_referenced
是否有被弱引用指向過(guò)涕蚤。如果沒(méi)有宪卿,釋放時(shí)會(huì)更快- deallocating
對(duì)象是否正在釋放- extra_rc
里面存儲(chǔ)的值是引用計(jì)數(shù)器減1- has_sidetable_rc
引用計(jì)數(shù)器是否過(guò)大無(wú)法存儲(chǔ)在isa中
如果為1,那么引用計(jì)數(shù)會(huì)存儲(chǔ)在一個(gè)叫SideTable的類的屬性中
2. class結(jié)構(gòu)
結(jié)構(gòu)體
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
struct objc_class : objc_object {
// Class ISA;
Class superclass; // 父類
cache_t cache; //方法緩存
class_data_bits_t bits; // 用于獲取具體的類的信息
}
查看源碼(只保留了主要代碼)
- class_rw_t
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods; //方法列表
property_array_t properties; //屬性列表
protocol_array_t protocols; // 協(xié)議列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
其中的methods万栅、properties佑钾、protocols
是二維數(shù)組,是可讀可寫的烦粒,包含了類的初始內(nèi)容休溶、分類的內(nèi)容。
- method_array_t
class method_array_t :
public list_array_tt<method_t, method_list_t>
{
typedef list_array_tt<method_t, method_list_t> Super;
public:
method_list_t **beginCategoryMethodLists() {
return beginLists();
}
method_list_t **endCategoryMethodLists(Class cls);
method_array_t duplicate() {
return Super::duplicate<method_array_t>();
}
};
方法列表 中存放著很多一維數(shù)組method_list_t,而每一個(gè)method_list_t中存放著method_t邮偎。method_t中是對(duì)應(yīng)方法的imp指針管跺、名字、類型等方法信息禾进。
- method_t
struct method_t {
SEL name; //函數(shù)名
const char *types; //編碼(返回值類型豁跑,參數(shù)類型)
MethodListIMP imp; //指向函數(shù)的指針(函數(shù)地址)
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
IMP
:代表函數(shù)的具體實(shí)現(xiàn)
SEL
:代表方法、函數(shù)名泻云,一般叫做選擇器艇拍。
types
:包含了函數(shù)返回值、參數(shù)編碼的字符串
關(guān)于SEL:
可以通過(guò)@selector()
和sel_registerName()
獲得
可以通過(guò)sel_getName()
和NSStringFromSelector()
轉(zhuǎn)成字符串
不同類中相同名字的方法宠纯,所對(duì)應(yīng)的方法選擇器是相同的卸夕。即,不同類的相同SEL是同一個(gè)對(duì)象婆瓜。
- class_ro_t
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;//instance對(duì)象占用的內(nèi)存空間
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; //類名
method_list_t * baseMethodList; //方法列表
protocol_list_t * baseProtocols; //協(xié)議列表
const ivar_list_t * ivars; //成員變量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
class_ro_t里面的baseMethodList快集、baseProtocols、ivars廉白、baseProperties
是一維數(shù)組个初,是只讀的,包含了類的初始內(nèi)容
3. Type Encoding
iOS中提供了一個(gè)叫做@encode的指令猴蹂,可以將具體的類型表示成字符串編碼院溺。比如:
+(int)testWithNum:(int)num{
return num;
}
上面的方法可以用 i20@0:8i16
來(lái)表示:
i表示返回值是int類型,20是參數(shù)總共20字節(jié)
@表示第一個(gè)參數(shù)是id類型磅轻,0表示第一個(gè)參數(shù)從第0個(gè)字節(jié)開(kāi)始
:表示第二個(gè)參數(shù)是SEL類型珍逸。8表示第二個(gè)參數(shù)從第8個(gè)字節(jié)開(kāi)始。
i表示第三個(gè)參數(shù)是int類型聋溜,16表示第三個(gè)參數(shù)從第16個(gè)字節(jié)開(kāi)始
第三個(gè)參數(shù)從第16個(gè)字節(jié)開(kāi)始谆膳,是Int類型,占用4字節(jié)勤婚∧×浚總共20字節(jié)
4. 方法緩存
用散列表來(lái)緩存曾經(jīng)調(diào)用過(guò)的方法,可以提高方法的查找速度馒胆。
結(jié)構(gòu)體 cache_t
struct cache_t {
struct bucket_t *_buckets; // 散列表
mask_t _mask; //散列表的長(zhǎng)度 -1
mask_t _occupied; //已經(jīng)緩存的方法數(shù)量
}
// 其中的 散列表
struct bucket_t {
MethodCacheIMP _imp; //函數(shù)的內(nèi)存地址
cache_key_t _key; //SEL作為Key
}
- cache_t中如何查找方法
// 散列表中查找方法緩存
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m);
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
其中缨称,根據(jù)key和散列表長(zhǎng)度減1 mask 計(jì)算出下標(biāo) key & mask,取出的值如果key和當(dāng)初傳進(jìn)來(lái)的Key相同祝迂,就說(shuō)明找到了睦尽。否則,就不是自己要找的方法型雳,就有了hash沖突当凡,把i的值加1山害,繼續(xù)計(jì)算。如下代碼:
// 計(jì)算下標(biāo)
static inline mask_t cache_hash(cache_key_t key, mask_t mask)
{
return (mask_t)(key & mask);
}
//hash沖突的時(shí)候
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask;
}
- cache_t的擴(kuò)容
當(dāng)方法緩存太多的時(shí)候沿量,超過(guò)了容量的3/4s時(shí)候浪慌,就需要擴(kuò)容了。擴(kuò)容是朴则,把原來(lái)的容量增加為2倍权纤。
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
...
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// Cache is less than 3/4 full. Use it as-is.
}
else {
// 來(lái)到這里說(shuō)明,超過(guò)了3/4,需要擴(kuò)容
cache->expand();
}
...
}
// 擴(kuò)容
enum {
INIT_CACHE_SIZE_LOG2 = 2,
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2)
};
// cache_t的擴(kuò)容
void cache_t::expand()
{
cacheUpdateLock.assertLocked();
uint32_t oldCapacity = capacity();
// 擴(kuò)容為原來(lái)的2倍
uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
if ((uint32_t)(mask_t)newCapacity != newCapacity) {
// mask overflow - can't grow further
// fixme this wastes one bit of mask
newCapacity = oldCapacity;
}
reallocate(oldCapacity, newCapacity);
}
5. 消息轉(zhuǎn)發(fā)機(jī)制
OC方法調(diào)用的本質(zhì)是乌妒,消息轉(zhuǎn)發(fā)機(jī)制汹想。比如:
對(duì)象instance 調(diào)用dotest方法[instance1 dotest];
底層會(huì)轉(zhuǎn)化為:objc_msgSend(instance1, sel_registerName("dotest"));
OC中方法的調(diào)用,其實(shí)都是轉(zhuǎn)換為objc_msgSend
函數(shù)的調(diào)用撤蚊。
實(shí)例對(duì)象中存放著 isa 指針以及實(shí)例變量古掏。由 isa 指針找到實(shí)例對(duì)象所屬的類對(duì)象 (類也是對(duì)象)。類中存放著實(shí)例方法列表侦啸。在這個(gè)列表中槽唾,方法的保存形式是SEL
作 key,IMP
作value匹中。
這是在編譯時(shí)根據(jù)方法名夏漱,生成唯一標(biāo)識(shí)
SEL
,IMP
其實(shí)就是函數(shù)指針 顶捷,指向最終的函數(shù)實(shí)現(xiàn)。
整個(gè) Runtime 的核心就是 objc_msgSend(receiver, @selector (message))
函數(shù)屎篱,通過(guò)給類發(fā)送 SEL
以傳遞消息服赎,找到匹配的IMP
再獲取最終的實(shí)現(xiàn)。
執(zhí)行流程可以分為3大階段:消息發(fā)送->動(dòng)態(tài)方法解析->消息轉(zhuǎn)發(fā)
- 消息發(fā)送階段:
首先判斷receiver是否為空
如果不為空交播,從receiverClass的緩存中重虑,查找方法。(找到了就調(diào)用)
如果沒(méi)找到秦士,就從receiverClass的class_rw_t
中查找方法缺厉。(找到就調(diào)用,并緩存)
如果沒(méi)找到隧土,就去receiverClassd的父類的緩存中查找提针。
如果沒(méi)找到,就從父類的class_rw_t
中查找方法曹傀。
如果沒(méi)找到辐脖,就看是否還有父類,有就繼續(xù)查父類的緩存皆愉,方法列表嗜价。
由上述知道艇抠,去查緩存、方法列表久锥、查父類等這些操作之后家淤,都沒(méi)有找到這個(gè)方法的實(shí)現(xiàn),這時(shí)如果后面不做處理瑟由,必然拋出異常:
...due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[xxx xxxx]: unrecognized selector sent to instance 0x100f436c0’
如果沒(méi)有父類媒鼓,說(shuō)明消息發(fā)送階段結(jié)束,那么就進(jìn)入第二階段错妖,動(dòng)態(tài)方法解析階段绿鸣。
- 動(dòng)態(tài)方法解析:
在此,可以給未找到的方法暂氯,動(dòng)態(tài)綁定方法實(shí)現(xiàn)潮模。或者給某個(gè)方法重定向痴施。
源碼:
// 動(dòng)態(tài)方法解析
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) { //如果不是元類對(duì)象
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else { // 是元類對(duì)象
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
其中的resolveClassMethod
和resolveInstanceMethod
默認(rèn)是返回NO
+ (BOOL)resolveClassMethod:(SEL)sel {
return NO;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}
- 在動(dòng)態(tài)解析階段擎厢,可以重寫
resolveInstanceMethod
并添加方法的實(shí)現(xiàn)。
假如辣吃,沒(méi)有找到run
這個(gè)方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector( run )) {
// 獲取其他方法 實(shí)例方法 或類方法动遭,作為run的實(shí)現(xiàn)
Method method = class_getInstanceMethod(self, @selector(test));
// 動(dòng)態(tài)添加test方法的實(shí)現(xiàn)
class_addMethod(self, sel,
method_getImplementation(method),
method_getTypeEncoding(method));
// 返回YES代表有動(dòng)態(tài)添加方法 其實(shí)這里返回NO,也是可以的神得,返回YES只是增加了一些打印
return NO;
}
return [super resolveInstanceMethod:sel];
}
上面的代碼厘惦,就相當(dāng)于,調(diào)用run的時(shí)候哩簿,實(shí)際上調(diào)用的是test宵蕉。
如果前面消息發(fā)送 和動(dòng)態(tài)解析階段,都沒(méi)有對(duì)方法進(jìn)行處理节榜,我們還有最后一個(gè)階段羡玛。如下
- 消息轉(zhuǎn)發(fā)
____forwarding___
這個(gè)函數(shù)中,交代了消息轉(zhuǎn)發(fā)的邏輯宗苍。但是不開(kāi)源稼稿。
先判斷forwardingTargetForSelector
的返回值。有讳窟,就向這個(gè)返回值發(fā)送消息让歼,讓它調(diào)用方法。
如果返回nil
挪钓,就調(diào)用methodSignatureForSelector
方法是越,有就調(diào)用forwardInvocation
。
其中的參數(shù)是一個(gè)
NSInvocation
對(duì)象碌上,并將消息全部屬性記錄下來(lái)倚评。NSInvocation
對(duì)象包括了Selector浦徊、target
以及其他參數(shù)。其中的實(shí)現(xiàn)僅僅是改變了target
指向天梧,使消息保證能夠調(diào)用盔性。
倘若發(fā)現(xiàn)本類無(wú)法處理,則繼續(xù)查找父類呢岗,直至 NSObject
冕香。如果methodSignatureForSelector
方法返回nil
,就調(diào)用doesNotRecognizeSelector:
方法后豫。
應(yīng)用舉例:
場(chǎng)景1:
類Person只定義了方法run但沒(méi)有實(shí)現(xiàn)悉尾,另外有類Car實(shí)現(xiàn)了方法run。
現(xiàn)在Person中挫酿,重寫forwardingTargetForSelector
返回Car對(duì)象
// 消息轉(zhuǎn)發(fā)
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(run)) {
return [[Car alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
這時(shí)构眯,當(dāng)person實(shí)例調(diào)用run方法時(shí),會(huì)變成car實(shí)例調(diào)用run方法早龟。
證明forwardingTargetForSelector
返回值不為空的話惫霸,就向這個(gè)返回值發(fā)送消息,也就是objc_msgSend(返回值, SEL)
葱弟。
場(chǎng)景2:
如果前面的forwardingTargetForSelector
返回為空壹店。底層就會(huì)調(diào)用 methodSignatureForSelector
獲取方法簽名后,再調(diào)用 forwardInvocation
芝加。
因此:可以重寫這兩個(gè)方法:
// 方法簽名:返回值類型硅卢、參數(shù)類型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(run)) {
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
[anInvocation invokeWithTarget:[[Car alloc] init]];
}
這樣,依然可以調(diào)用到car的run方法妖混。
NSInvocation封裝了一個(gè)方法調(diào)用老赤,包括:方法調(diào)用者、方法名制市、方法參數(shù)
anInvocation.target 方法調(diào)用者
anInvocation.selector 方法名
[anInvocation getArgument:NULL atIndex:0]
補(bǔ)充:
1、消息轉(zhuǎn)發(fā)的forwardingTargetForSelector弊予、methodSignatureForSelector祥楣、forwardInvocation
不僅支持實(shí)例方法,還支持類方法汉柒。不過(guò)系統(tǒng)沒(méi)有提示误褪,需要寫成實(shí)例方法,然后把前面的-改成+即可碾褂。
+(IMP)instanceMethodForSelector:(SEL)aSelector{
}
-(IMP)methodForSelector:(SEL)aSelector{
}
2兽间、只能向運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建的類添加ivars
,不能向已經(jīng)存在的類添加ivars
正塌。 這是因?yàn)樵诰幾g時(shí)嘀略,只讀結(jié)構(gòu)體class_ro_t
就被確定恤溶,在運(yùn)行時(shí)不可更改。