1悴务、編譯時
編譯階段,會通過編譯器將語言轉(zhuǎn)化成機器碼譬猫,然后直接在CPU上執(zhí)行機器碼惨寿,效率
更高.
OC便是使用編譯器進行處理,生成可執(zhí)行文件删窒;
而像Python等語言則是使用解釋器,在運行時解釋執(zhí)行代碼顺囊,將代碼翻譯成目標代
碼肌索,然后再一句一句的去執(zhí)行目標代碼,所以相對來說效率就低特碳,但是跑起來后不用
重啟編譯代碼诚亚,直接修改代碼就可以看到效果。
編譯過程的詳細信息請移步
2午乓、為什么說OC是動態(tài)語言
靜態(tài)語言
站宗,是在編譯期已經(jīng)決定了一切,在運行時只是遵守在編譯器確定好的指令
在進行而已;
動態(tài)語言
益愈,只是在編譯期做語義語法的檢查梢灭,而不會分配內(nèi)存,在編譯期只關(guān)心對
象是否調(diào)用這個方法蒸其,而不會關(guān)心這個對象是否為nil敏释, 也不會關(guān)心這個方法的具體
實現(xiàn)以及這個對象的本質(zhì)是否能調(diào)用該方法,更不會關(guān)心有沒有這個方法實現(xiàn)摸袁,這些
事情都是在運行時進行的.
OC的動態(tài)性體現(xiàn)在三個方面:
動態(tài)類型
钥顽,動態(tài)綁定
和動態(tài)加載
-
動態(tài)類型
,在運行時才確定對象的類型靠汁。如'NSString *data = [[NSData alloc] init]' 編譯時是字符串類型蜂大,運行時確定真正類型是data類型 -
動態(tài)綁定
闽铐,基于動態(tài)類型,在對象被確定后奶浦,其類型也被確定了兄墅,則該對象對應(yīng)的屬性和響應(yīng)小時也就確定了 -
動態(tài)加載
,根據(jù)需求加載所需資源财喳,讓程序在運行時添加代碼模塊以及其他資源, 用戶可以根據(jù)需要加載一些可執(zhí)行代碼和資源察迟,而不是在啟動時就加載所有組件
3、對象的底層結(jié)構(gòu)
isa
指針耳高,實例對象通過它的isa
指針找到它的類扎瓶,類對象通過它找到它的元類,元類指向根類
isa
指針確定所屬類泌枪,super_class 確定繼承關(guān)系概荷。
/// 實例對象的底層結(jié)構(gòu)
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// 類對象的底層結(jié)構(gòu)
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
};
/// isa
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
/// 對象結(jié)構(gòu)
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
uintptr_t isaBits() const;
// 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 /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=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 hasNonpointerIsa();
bool isTaggedPointer();
bool isBasicTaggedPointer();
bool isExtTaggedPointer();
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 nonpointer, bool hasCxxDtor);
// Slow paths for inline control
id rootAutorelease2();
uintptr_t 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);
uintptr_t 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
};
從上面的結(jié)構(gòu)體信息,可以知道碌燕,每個對象的本質(zhì)都是一個結(jié)構(gòu)體误证,都有一個isa指
針指向?qū)?yīng)的類
4、消息發(fā)送的基礎(chǔ)數(shù)據(jù)
SEL
修壕,方法選擇器愈捅,是一個方法的selector
指針,用于表示運行時下方法的名稱慈鸠,定義如下:
typedef struct objc_selector *SEL;
1蓝谨、底層維護一張選擇子表(
SEL
)
2、OC編譯時青团,會依據(jù)每個方法的名稱和參數(shù)生成一個唯一整型標識(int類型的地
址)譬巫,這個標識就是SEL
3、兩個類之間督笆,不管有沒有關(guān)系芦昔,只要方法名相同,對應(yīng)的SEL地址是一樣的娃肿,所以
在OC同一個類(及類的繼承體系)中咕缎,不能存在2個同名的方法,即使參數(shù)類型不同也
不行(同一個類使用相同的方法名料扰,即便參數(shù)不同锨阿,編譯時也會報錯)
4、不同類方法名可以相同记罚,因為方法查找會根據(jù)SEL從各自的類方法列表中查詢對應(yīng)
的IMP
/// SEL方法調(diào)用
// 返回給定選擇器指定的方法的名稱
const char * sel_getName ( SEL sel );
/*
向Runtime系統(tǒng)中注冊一個方法墅诡,如果已注冊則直接返回對應(yī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
函數(shù)指針烟馅,指向方法實現(xiàn)的首地址
id (*IMP)(id, SEL, ...)
Method
表示類定義中的方法
struct objc_method {
/// 方法名
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
/// 方法類型
char * _Nullable method_types OBJC2_UNAVAILABLE;
/// 方法實現(xiàn)
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
/// Method 方法調(diào)用
// 調(diào)用指定方法的實現(xiàn),返回的是實際實現(xiàn)的返回值然磷。參數(shù)receiver不能為空郑趁。這個方法的效率會比method_getImplementation和method_getName更快
id method_invoke ( id receiver, Method m, ... );
// 調(diào)用返回一個數(shù)據(jù)結(jié)構(gòu)的方法的實現(xiàn)
void method_invoke_stret ( id receiver, Method m, ... );
// 獲取方法名
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 );
// 返回指定方法的方法描述結(jié)構(gòu)體
struct objc_method_description * method_getDescription ( Method m );
// 設(shè)置方法的實現(xiàn)
IMP method_setImplementation ( Method m, IMP imp );
// 交換兩個方法的實現(xiàn)
void method_exchangeImplementations ( Method m1, Method m2 );
5、消息發(fā)送機制
消息發(fā)送流程:
- 1姿搜、編譯器將消息發(fā)送轉(zhuǎn)化成
objct_msgSend(id , SEL, ...)
寡润,通過匯編代碼
一步步處理最終會走到這個方法lookUpImpOrForward
(最新的源碼) - 2、先判斷當期那對象是否為nil舅柜,是則直接返回nil
- 3梭纹、在運行時去執(zhí)行時,該對象(實例或類)會先通過isa指針指向?qū)?yīng)的類(類或元
類) - 4致份、通過
SEL
簽名在cache_t
中進行查找变抽,有則返回 - 5、沒有再去
method_list
中進行查找氮块,有則添加到緩存中,并回調(diào) - 6绍载、沒有就通過
super_class
找到父類,繼續(xù)進行查找滔蝉,一直循環(huán)遍歷击儡,直到根類NSObject
- 7、以上都未能找到蝠引,則進行動態(tài)解析阳谍,最后進入消息轉(zhuǎn)發(fā)
異常提示IMP:
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
/// 截取部分匯編源碼,可以看到lookUpImpOrForward調(diào)用的基本信息
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
/*
_objc_msgForward_impcache 這里通過匯編發(fā)現(xiàn)最終執(zhí)行了
objc_defaultForwardHandler方法調(diào)用立肘,主要用來進行異常回調(diào)
*/
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
///添加線程鎖名扛,防止多線程操作
runtimeLock.assertUnlocked();
//如果當前的行為是查找緩存谅年,直接查找當前方法緩存
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
runtimeLock.lock();
/// 類的懶加載(為查找方法做準備條件,如果類沒有初始化時肮韧,初始化類和父類融蹂、元
/// 類等),對是否有swift進行不同的處理
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
}
/// 類的初始化操作
if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
runtimeLock.assertLocked();
curClass = cls;
/// 循環(huán)遍歷類-父類進行查找
for (unsigned attempts = unreasonableClassCount();;) {
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
// 查找當前類中的方法列表弄企,如果方法存在超燃,則跳轉(zhuǎn)到緩存插入處理
imp = meth->imp;
goto done;
}
/// 對當前類進行重新賦值,查找父類
if (slowpath((curClass = curClass->superclass) == nil)) {
// No implementation found, and method resolver did not help.
// Use forwarding.
imp = forward_imp;
break;
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but do not cache yet; call method
// resolver for this class first.
break;
}
if (fastpath(imp)) {
/// 查詢到父類的緩存中存在該方法拘领,則直接插入到當前類緩存中
goto done;
}
}
//這里進入消息轉(zhuǎn)發(fā)
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
/// 緩存插入
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
- 從上面
lookUpImpOrForward
方法查找過程中意乓,我們可以看到對類或者父類方法列表
進行方法查詢用到了getMethodNoSuper_nolock
這個方法: -
getMethodNoSuper_nolock
,對search_method_list_inline
進行遍歷,查詢對應(yīng)的方法
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
// <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
// caller of search_method_list, inlining it turns
// getMethodNoSuper_nolock into a frame-less function and eliminates
// any store from this codepath.
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
-
search_method_list_inline
,在所有方法列表中(自身约素,categorys)使用二分法或遍歷逐一尋找以name屬性值為sel的method_t(Method)届良,如果找到笆凌,以sel為鍵把method存入cache_t, 直接執(zhí)行mehtod里的IMP;
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
// 有序的話 進行二分查找(折半遍歷查找)
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
// 無序則進行線性遍歷查找
for (auto& meth : *mlist) {
/// 對比方法名的地址
if (meth.name == sel) return &meth;
}
}
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
-
findMethodInSortedMethodList
,二分查找的具體實現(xiàn)
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
ASSERT(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
// 分類與類的同名方法,分類會放到最前面士葫,所以這里進行'--'操作后乞而,進行往前查找
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
方法緩存相關(guān)內(nèi)容請移步
6、消息轉(zhuǎn)發(fā)
-
動態(tài)方法解析
resolveMethod_locked
/// 如果這個類不是元類則執(zhí)行__class_resolveInstanceMethod,如果是元類
/// 的話則執(zhí)行__class_resolveClassMethod,所以如果是類方法的話執(zhí)行resolveClassMethod,
/// 那么會一直查找最后還會執(zhí)行一次resolveinstancemethod,但是這個方法是
/// 執(zhí)行的元類的resolveinstancemethod而不是類的慢显,因為類是元類對象如果
/// 元類找不到就會往上層查找爪模,元類的上層是根元類,根元類的父類指向NSObject荚藻,
/// 所以最后還會執(zhí)行一次resolveInstanceMethod最終的實例方法屋灌。
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
1、嘗試調(diào)用自身的_class_resolveMethod動態(tài)為類對象或元類對象里添加方法實
現(xiàn)鞋喇。如果成功添加了method声滥,記錄已經(jīng)添加過
2、看是否在resolveClassMethod 或者 resolveInstanceMethod方法中為sel
提供了動態(tài)方法決議(可通class_addMethod方法添加)侦香,如果沒有提供則進入下
一步
3落塑、解析完成后繼續(xù)調(diào)用lookUpImpOrForward完成以下操作
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
-
備用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector
該方法只能讓我們把消息轉(zhuǎn)發(fā)到另一個能處理這個消息的對象,但是無法處理消息
的內(nèi)容罐韩,比如參數(shù)和返回值該方法用于把消息轉(zhuǎn)發(fā)到另外一個可以處理該消息的對象憾赁,如果該
aSelector
在
另一個對象中實現(xiàn)了,則該對象就作為該消息新的接收值散吵,直接返回該對象龙考。-
完整消息轉(zhuǎn)發(fā)
運行時系統(tǒng)在這一步讓消息接收者將消息轉(zhuǎn)發(fā)給其他對象。當前對象會將對應(yīng)的
sel -ector
矾睦,目標(target
)和參數(shù)打包成anInvocation
對象轉(zhuǎn)發(fā)給其他對象
消息轉(zhuǎn)發(fā)機制使用從這個方法中獲取的信息來創(chuàng)建NSInvocation對象
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
if ([XXX instancesRespondToSelector:aSelector]) {
signature = [XXX instanceMethodSignatureForSelector:aSelector];
}
}
return signature;
}
forwardInvocation
:方法的實現(xiàn)有兩個任務(wù):
(1)定位可以響應(yīng)封裝在anInvocation
中的消息的對象晦款。這個對象不需要能處理所
有未知消息。
(2)使用anInvocation
作為參數(shù)枚冗,將消息發(fā)送到選中的對象缓溅。anInvocation
將
會保留調(diào)用結(jié)果,運行時系統(tǒng)會提取這一結(jié)果并將其發(fā)送到消息的原始發(fā)送者
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL sel = anInvocation.selector;
XXX *x = [XXX new];
if ([XXX instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:x];
}
}
如果以上均未實現(xiàn)赁温,則無法識別
selector
坛怪,并拋出異常
7、runtime實踐:
1股囊、獲取對象的屬性袜匿、成員變量、方法和協(xié)議列表
2稚疹、通過消息轉(zhuǎn)發(fā)機制進行方法的攔截
3居灯、動態(tài)添加方法(class_addMethod
)
4、方法的動態(tài)交換(method-swizzling
)
5、將當前類與自定義類建立映射(Class object_setClass(id obj, Class cls
)穆壕,由runtime的說明可知該函數(shù)的作用是為一個對象設(shè)置一個指定的類待牵,object內(nèi)部有一個叫做isa
的變量指向它的class
。這個變量可以被改變喇勋,而不需要重新創(chuàng)建缨该。然后就可以添加新的ivar和方法了〈ū常可以通過以下命令來修改一個object的class)
例:object_setClass([NSBundle mainBundle],[ModifyBundle class])
贰拿,
NSBundle與ModifyBundle建立映射關(guān)系,將bundle的isa指針指向[ModifyBundle class]類熄云,在自定義Bundle類中重寫加載bundle
對應(yīng)的加載自建的XML類文件膨更、加載xib storyboard plist等文件、加載本地多
語言文件(.strings)等方法缴允,實現(xiàn)全局的多語言處理
8荚守、疑難解析
為什么要設(shè)計元類
如果沒有元類,則類對象的信息和實例對象的信息都會存儲在類中练般,這樣對于查找對
應(yīng)的信息矗漾,會更加復(fù)雜且混亂(如類方法和實例方法一樣的情況下,需要區(qū)分查找對
象的類型)依次類推薄料,實例對象的方法列表在類對象的結(jié)構(gòu)體中敞贡,如果直接放在實例對象中,那
么同一個類對象可能生成多個實例對象摄职,這些實例對象調(diào)用對應(yīng)的方法時誊役,內(nèi)存將會被
爆掉的綜上,元類的設(shè)計符合單一原則和可擴展性谷市,實例對象管理自己的屬性信息蛔垢,實例對
象的方法列表由類管理,類對象方法列表由元類管理迫悠,各司其職鹏漆,減少了對于對象類
型和方法類型的判斷,增加了查找效率及皂;不同種類的方法走同一套流程甫男,大大節(jié)約了
維護成本且改。