1火焰、runtime(運行時機制)是什么
runtime是屬于OC的底層,是一套比較底層的純C語言API, 屬于1個C語言庫, 包含了很多底層的C語言API胧沫,可以進行一些非常底層的操作(用OC是無法現(xiàn)實的, 不好實現(xiàn))昌简。 在我們平時編寫的OC代碼中, 程序運行過程時, 其實最終都是轉(zhuǎn)成了runtime的C語言代碼, runtime算是OC的幕后工作者。
-
例如:
// OC : [[MJPerson alloc] init] // runtime : objc_msgSend(objc_msgSend("MJPerson" , "alloc"), "init")
2琳袄、runtime知識圖譜
3江场、runtime數(shù)據(jù)結構
- 3.1、objc_object
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*indexed=false*/);
void initClassIsa(Class cls /*indexed=maybe*/);
void initProtocolIsa(Class cls /*indexed=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
// changeIsa() should be used to change the isa of existing objects.
// If this is a new object, use initIsa() for performance.
Class changeIsa(Class newCls);
bool hasIndexedIsa();
bool isTaggedPointer();
bool isClass();
// object may have associated objects?
bool hasAssociatedObjects();
void setHasAssociatedObjects();
// object may be weakly referenced?
bool isWeaklyReferenced();
void setWeaklyReferenced_nolock();
// object may have -.cxx_destruct implementation?
bool hasCxxDtor();
// Optimized calls to retain/release methods
id retain();
void release();
id autorelease();
// Implementations of retain/release methods
id rootRetain();
bool rootRelease();
id rootAutorelease();
bool rootTryRetain();
bool rootReleaseShouldDealloc();
uintptr_t rootRetainCount();
// Implementation of dealloc methods
bool rootIsDeallocating();
void clearDeallocating();
void rootDealloc();
private:
void initIsa(Class newCls, bool indexed, bool hasCxxDtor);
// Slow paths for inline control
id rootAutorelease2();
bool overrelease_error();
#if SUPPORT_NONPOINTER_ISA
// Unified retain count manipulation for nonpointer isa
id rootRetain(bool tryRetain, bool handleOverflow);
bool rootRelease(bool performDealloc, bool handleUnderflow);
id rootRetain_overflow(bool tryRetain);
bool rootRelease_underflow(bool performDealloc);
void clearDeallocating_slow();
// Side table retain count overflow for nonpointer isa
void sidetable_lock();
void sidetable_unlock();
void sidetable_moveExtraRC_nolock(size_t extra_rc, bool isDeallocating, bool weaklyReferenced);
bool sidetable_addExtraRC_nolock(size_t delta_rc);
size_t sidetable_subExtraRC_nolock(size_t delta_rc);
size_t sidetable_getExtraRC_nolock();
#endif
// Side-table-only retain count
bool sidetable_isDeallocating();
void sidetable_clearDeallocating();
bool sidetable_isWeaklyReferenced();
void sidetable_setWeaklyReferenced_nolock();
id sidetable_retain();
id sidetable_retain_slow(SideTable& table);
uintptr_t sidetable_release(bool performDealloc = true);
uintptr_t sidetable_release_slow(SideTable& table, bool performDealloc = true);
bool sidetable_tryRetain();
uintptr_t sidetable_retainCount();
#if DEBUG
bool sidetable_present();
#endif
};
- 3.2窖逗、objc_class
struct objc_class : objc_object {
Class superclass;
const char *name;
uint32_t version;
uint32_t info;
uint32_t instance_size;
struct old_ivar_list *ivars;
struct old_method_list **methodLists;
Cache cache;
struct old_protocol_list *protocols;
// CLS_EXT only
const uint8_t *ivar_layout;
struct old_class_ext *ext;
void setInfo(uint32_t set) {
OSAtomicOr32Barrier(set, (volatile uint32_t *)&info);
}
void clearInfo(uint32_t clear) {
OSAtomicXor32Barrier(clear, (volatile uint32_t *)&info);
}
// set and clear must not overlap
void changeInfo(uint32_t set, uint32_t clear) {
assert((set & clear) == 0);
uint32_t oldf, newf;
do {
oldf = this->info;
newf = (oldf | set) & ~clear;
} while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&info));
}
bool hasCxxCtor() {
// set_superclass propagates the flag from the superclass.
return info & CLS_HAS_CXX_STRUCTORS;
}
bool hasCxxDtor() {
return hasCxxCtor(); // one bit for both ctor and dtor
}
bool hasCustomRR() {
return true;
}
void setHasCustomRR(bool = false) { }
void setHasDefaultRR() { }
void printCustomRR(bool) { }
bool hasCustomAWZ() {
return true;
}
void setHasCustomAWZ(bool = false) { }
void setHasDefaultAWZ() { }
void printCustomAWZ(bool) { }
bool instancesHaveAssociatedObjects() {
return info & CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS;
}
void setInstancesHaveAssociatedObjects() {
setInfo(CLS_INSTANCES_HAVE_ASSOCIATED_OBJECTS);
}
bool shouldGrowCache() {
return info & CLS_GROW_CACHE;
}
void setShouldGrowCache(bool grow) {
if (grow) setInfo(CLS_GROW_CACHE);
else clearInfo(CLS_GROW_CACHE);
}
bool shouldFinalizeOnMainThread() {
return info & CLS_FINALIZE_ON_MAIN_THREAD;
}
void setShouldFinalizeOnMainThread() {
setInfo(CLS_FINALIZE_ON_MAIN_THREAD);
}
// +initialize bits are stored on the metaclass only
bool isInitializing() {
return getMeta()->info & CLS_INITIALIZING;
}
// +initialize bits are stored on the metaclass only
void setInitializing() {
getMeta()->setInfo(CLS_INITIALIZING);
}
// +initialize bits are stored on the metaclass only
bool isInitialized() {
return getMeta()->info & CLS_INITIALIZED;
}
// +initialize bits are stored on the metaclass only
void setInitialized() {
getMeta()->changeInfo(CLS_INITIALIZED, CLS_INITIALIZING);
}
bool isLoadable() {
// A class registered for +load is ready for +load to be called
// if it is connected.
return isConnected();
}
IMP getLoadMethod();
bool isFuture();
bool isConnected();
const char *mangledName() { return name; }
const char *demangledName() { return name; }
const char *nameForLogging() { return name; }
bool isMetaClass() {
return info & CLS_META;
}
// NOT identical to this->ISA() when this is a metaclass
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() {
return instance_size;
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
return (unalignedInstanceSize() + WORD_MASK) & ~WORD_MASK;
}
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
};
- 3.3址否、isa
- cache_t是用來存放每一個類中的方法緩存的數(shù)據(jù)結構,其本質(zhì)是一個可增量擴展的哈希表數(shù)據(jù)結構,使用cache_t可以快速查找方法的執(zhí)行函數(shù)佑附,也是局部性原理的最佳應用樊诺。試想一下,如果一個類中有眾多的類方法與對象方法音同,有些方法可能在程序執(zhí)行的生命周期中只會調(diào)用一次词爬,而在其他方法每一次調(diào)用時,系統(tǒng)都會遍歷類中所有的方法后找到其方法的相應IMP進行執(zhí)行权均,是一個非常浪費性能的工作顿膨,所以以此原因引入了cache_t,緩存方法列表叽赊,系統(tǒng)將一些常用方法儲存在類的緩存列表cache_t中恋沃,在每次方法調(diào)用時,先從緩存列表中查找必指,如果找到了對應方法囊咏,就直接調(diào)用其函數(shù)體,這是一種非常節(jié)約計算成本的方式塔橡。
- 關于cache_t中的的數(shù)據(jù)結構梅割,使用的一張哈希表存儲的眾多bucket_t數(shù)據(jù)結構,在bucket_t中包含的是方法的實現(xiàn)IMP與其對應的key葛家,在一次消息傳遞的過程中户辞,系統(tǒng)首先會在對應的類中進行緩存查找,利用發(fā)送消息的SEL查找cache_t哈希表惦银,其過程是通過計算將SEL轉(zhuǎn)換成一個cache_key_t對象咆课,利用該"key"進行cache_t哈希表定位,隨后取出bucket_t扯俱,直接拿到IMP指針书蚪,進行消息的發(fā)送。
- class_data_bits_t數(shù)據(jù)結構迅栅。class_data_bits_t主要的作用是包含類中的方法殊校、成員變量、協(xié)議等諸多主要信息读存,隨后再對一些零散信息的封裝为流。其實class_data_bits_t的主要作用也是對class_rw_t的一個封裝,重要的信息其實都在class_rw_t中
- class_rw_t數(shù)據(jù)結構让簿。class_rw_t代表了類相關的讀寫信息敬察,以及對class_ro_t的封裝。顧名思義尔当,rw就是讀寫莲祸,ro就是只讀。class_rw_t內(nèi)的數(shù)據(jù)結構有,class_ro_t,protocols,properties,methods锐帜,第一個class_ro_t稍后詳細說明田盈。后面三個其實就是對協(xié)議、屬性缴阎、方法的封裝允瞧,這三個數(shù)據(jù)結構都是以二維數(shù)組的形式提現(xiàn)的,但是為什么是二維數(shù)組呢蛮拔?我之前寫過一篇關于分類理解的文章述暂,里面說到分類方法添加的問題,就是分類在運行時決議的過程中建炫,會把分類的方法都以數(shù)組的形式都添加到類方法中贸典,其實過程就在這里,分類中眾多method_t以數(shù)組[method1,method2,method3]的形式提現(xiàn)踱卵,但一個類可能有眾多分類,那么分類1据过,分類2惋砂,分類3在objc_class的class_data_bits_t的class_rw_t中的體現(xiàn)就是[[method1,method2,method3],[method1,method2,method3],[method1,method2,method3]],其都是method_t的數(shù)據(jù)形式绳锅。協(xié)議西饵,屬性的體現(xiàn)方式都是相似的,就同理后推就好了鳞芙。所以到這里肯定會有一個疑問眷柔,為什么class_rw_t中沒有成員變量只有屬性。因為class_rw_t中包含的是一個讀寫數(shù)據(jù)的列表原朝,換言之其實就是分類的列表驯嘱,class_rw_t只管分類中的數(shù)據(jù),之前分類那篇文章說到過喳坠,為分類添加屬性是不會生成相應的成員變量的鞠评,如果要生成對應的成員變量必須用關聯(lián)對象技術把使其達到一個類似于可讀寫效果,而其關聯(lián)對象都儲存在一個全局容器中壕鹉,這也呼應了開頭介紹objc_object數(shù)據(jù)結構中存儲的相關關聯(lián)對象的方法剃幌。好了,越繞越遠了晾浴,現(xiàn)在繼續(xù)介紹class_ro_t數(shù)據(jù)結構
class_ro_t數(shù)據(jù)結構负乡。這個數(shù)據(jù)結構包含的信息就是類本身的信息,包括name(類名),ivars(instence variables成員變量列表),properties(屬性列表),protocols(協(xié)議列表),methodList(方法列表)脊凰。所以這下好理解了吧抖棘,class_ro_t中裝的是類本身編譯的信息,class_rw_t中裝的是類中分類的信息,而class_data_bits_t封裝了class_rw_t封裝了class_ro_t钉答。不過需要注意區(qū)別的是class_ro_t中包含的ivas,properties,protocols,methodList都是一位數(shù)組的形式存在(因為沒有分類了嘛)础芍。所以這里還需要注意一個點就是:我們沒法向一個編譯后的類動態(tài)添加信息,比如方法数尿,成員變量仑性,屬性等,第一是因為這些都存在于class_ro_t中右蹦,其名稱含義就告訴你readOnly诊杆,人好好的存在那,說了不讓你改何陆,你硬要改晨汹,那肯定不行啊,第二是因為所謂向一個類中動態(tài)添加信息贷盲,都是指的用runtime動態(tài)添加的類淘这,通俗點講就是用代碼寫的類
3.4、method_t
struct method_t {
SEL name;
const char *types;
IMP imp;
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; }
};
};
4巩剖、類對象與元類對象&消息傳遞相關面試問題
類對象存儲實例方法列表等信息铝穷。
元類對象存儲類方法列表等信息。
為什么會有元類呢佳魔?
- 因為在Objective-C中曙聂,對象的方法并沒有存儲在對象的結構體中(如果每個對象都存儲自己的方法,那我們程序中無數(shù)對象就都要存儲自己的方法鞠鲜,那內(nèi)存肯定就不夠用了)宁脊。當我們調(diào)用實例方法時,它通過自己的isa查找到對應的類贤姆,然后在class_data_bits_t結構體中查找對應的方法實現(xiàn)榆苞。每一個objc_class也有一個superClass指向自己的父類,可以查找到繼承的方法霞捡。那么如果調(diào)用實例方法怎么查找呢语稠?這時,就需要引入元類來保證無論調(diào)用類方法和實例方法弄砍,都可以以相同的方式來查找仙畦。
消息傳遞的機制
- 消息傳遞機制在runtime中的提現(xiàn)是一個方法objc_msgSend(object,SEL,types),當我們在OC中用[]調(diào)起一個方法的同時,runtime內(nèi)部就會去調(diào)取這個方法進行消息方法音婶,還有一個方法是objc_msgSendSuper慨畸,參數(shù)還是一樣,這個方法是我們用關鍵字super去調(diào)起方法時runtime內(nèi)部所執(zhí)行的方法衣式,其內(nèi)部包含一個變量receiver寸士,這個變量指向的就是super的子類self自己檐什。之前看過一個案例,是在init內(nèi)部調(diào)用[self class]和[super class]弱卡,然后同時打印這兩個類名乃正。其實結果不出意外是一樣的,但分析原因的話就是因為[super class]調(diào)起時婶博,調(diào)用的是objc_msgSendSuper這個方法瓮具,其中包含一個指針receiver,指向的就是super對象調(diào)用者self自己凡人,也就是消息的接收者還是self名党,而class方法都是存在于較高父類(系統(tǒng)類中的),所以在方法遍歷的時候挠轴,[super class]和[self class]的區(qū)別就在于传睹,super是從父類開始向上遍歷直至找到class方法,而self是從本類開始向上遍歷最后找到對應方法岸晦,而調(diào)用class最后的結果取決于消息的接收者欧啤,因為兩個方法的消息接收者都是self,所以不出意外启上,最后打印出的結果都是self類本身堂油。
而實際消息當一個方法調(diào)用的消息發(fā)出后,消息傳遞的機制為:
- 首先判斷方法為類方法還是實例方法 ->object找到父類class(若為類方法則再向上取到metaClass)->哈希查找objc_class的緩存列表cache_t試試能否用對用SEL直接找到函數(shù)體IMP->若找到碧绞,則直接進行函數(shù)體調(diào)用后結束消息放松,若找不到則遍歷對應的類或者元類的方法列表吱窝,如果方法列表是排序好的則使用二分法遍歷讥邻,如果沒有排序好則使用普通遍歷->如果找到了則進行函數(shù)體調(diào)用,結束消息傳遞流程院峡,若找不到則繼續(xù)根據(jù)superClass指針找到父類兴使,再重復之前的工作(遍歷緩存后遍歷本類中方法列表),在途中若找到了相應函數(shù)體實現(xiàn)IMP則進行調(diào)用照激,結束消息傳遞流程发魄,如果至到NSObject中還沒有找到則進行消息轉(zhuǎn)發(fā)的流程
-
緩存查找
- 1、給定值是
SEL
俩垃,目標值是對應bucket_t
中的IMP
励幼。
-
2、當前類中查找
1口柳、對于已排序好的列表苹粟,采用二分查找算法查找方法對應執(zhí)行函數(shù)。
2跃闹、對于沒有排序的列表嵌削,采用一般遍歷查找方法對應執(zhí)行函數(shù)毛好。
-
3、父類逐級查找
- 1、給定值是
5苛秕、消息轉(zhuǎn)發(fā)流程
關于消息轉(zhuǎn)發(fā)的流程肌访,其實是系統(tǒng)給的一個消息再利用的過程,當消息傳遞流程中沒有方法來響應此消息時艇劫,開發(fā)者可通過重寫以下這四個方法來實現(xiàn)消息轉(zhuǎn)發(fā)的過程吼驶,以及去讓計算機做相應的工作。
- 1港准、+ (BOOL)resolveInstanceMethod:(SEL)sel
若返回YES旨剥,則表明消息已處理,結束流程浅缸,若返回NO則進行下一個方法的調(diào)用
- 2轨帜、- (id)forwardingTargetForSelector:(SEL)aSelector
此方法可以返回轉(zhuǎn)發(fā)消息的目標,若返回為nil則調(diào)用下一個方法
- 3衩椒、- (NSMethodSignature *)methodSignatureForSelector
此方法可以返回方法的簽名蚌父,若返回nil則直接拋出異常,表明方法函數(shù)體指針無法被找到毛萌,若返回方法簽名苟弛,則調(diào)用下一個方法
- 4、- (void)forwardInvocation:(NSInvocation *)anInvocation
此方法內(nèi)部會決定是否已處理方法阁将,如果到這個方法也沒法處理膏秫,則拋出異常,報錯
以上四個方法是系統(tǒng)留給開發(fā)者處理消息轉(zhuǎn)發(fā)的入口做盅,開發(fā)者可以通過重寫以上四個方法來手動處理消息轉(zhuǎn)發(fā)的流程缤削。
6、Method-Swizzling
7吹榴、動態(tài)添加方法
8亭敢、動態(tài)方法解析
-
@dynamic
- 動態(tài)運行時語言將函數(shù)決議推遲到運行時。