官方文檔及資源地址
Documentation Archive
Apple Open Source
查看runtime開源文件 arm64 位
objc-msg-arm64.s - ARM64 code to support objc messaging
將Object-C 語言轉(zhuǎn)換為C++:
xcrun -sdk iphonesimulator clang -rewrite-objc main.m
以上指令 會生成.cpp文件 查看cpp文件代碼 與官方源碼 得知
objc_msgSend 是用匯編寫的卫病。
C語言不能通過寫一個函數(shù),去跳轉(zhuǎn)到任意的指針,匯編可以利用寄存器實現(xiàn)侈百,C語言使用“靜態(tài)綁定”胖喳,也就是說孝常,在編譯時就能決定運行時所應調(diào)用的函數(shù)七冲,如果待調(diào)用的函數(shù)地址無法硬編碼在指令之中背镇,那就要在運行期讀取出來锨用,使用“動態(tài)綁定”丰刊,我們都知道c語言是面向過程,由編譯器進行處理增拥,顯然無法實現(xiàn)這樣的需求啄巧。而runtime是運行時寻歧,在運行的時候會進行特殊操作訪問不同的內(nèi)存空間,因此oc具備動態(tài)特性秩仆。
1.對象及方法本質(zhì)
@autoreleasepool {
YJPerson * P = [YJPerson new];
[P run];
}
//編譯后 (環(huán)境依賴部分代碼暫不考慮 此處沒有粘貼出來)
#pragma clang assume_nonnull begin
#ifndef _REWRITER_typedef_YJPerson
#define _REWRITER_typedef_YJPerson
typedef struct objc_object YJPerson;
typedef struct {} _objc_exc_YJPerson;
#endif
struct YJPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
YJPerson * P = ((YJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YJPerson"), sel_registerName("new"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)P, sel_registerName("run"));
}
void runImp (id self ,SEL _cmd){
}
將.m文件轉(zhuǎn)換為C++文件 即可得出
- 對象的本質(zhì):結(jié)構(gòu)體码泛, 占用內(nèi)存大小
- 方法的本質(zhì)就是 _objc_msgSend 發(fā)消息
接下來 看下 消息傳遞與轉(zhuǎn)發(fā)。
2.消息傳遞
消息發(fā)送 _objc_msgSend
void objc_msgSend(id self, SEL cmd, ...)
//接受兩個或兩個以上的參數(shù),第一個參數(shù)代表接收者澄耍,第二個參數(shù)代表SEL(SEL是選擇子的類型)噪珊,后續(xù)參數(shù)就是消息中的那些參數(shù).編譯器會進行轉(zhuǎn)換
id returnValue = objc_msgSend(someObject,@selector(messageName:), parameter);
以下幾個概念需要搞清
- objc_class
重要成員(也都是結(jié)構(gòu)體 建議看下源碼 很有意思的)
- objc_method_list($)
方法列表 - objc_cache($)
緩存列表 method_name:method_imp 。 key:value的形式 - 結(jié)構(gòu)體里保存了指向父類的指針齐莲、類的名字痢站、版本、實例大小选酗、實例變量列表阵难、方法列表、緩存芒填、遵守的協(xié)議列表等
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
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;
struct objc_method_list {
struct objc_method_list *obsolete;
int method_count;
#ifdef __LP64__
int space;
#endif
/* variable length structure */
struct objc_method method_list[1];
};
struct objc_method {
SEL method_name;
char *method_types; /* a string representing argument/return types */
IMP method_imp;
};
- 源碼部分分析
調(diào)用 objc_msgSend 后 系統(tǒng)會進行一系列的復雜操作
- 首先呜叫,通過 obj 的 isa 指針找到它的 class ;
- 在 class 的 method list 找 對應的 func ;
- 如果 class 中沒到 func,繼續(xù)往它的 superclass 中找 ;
- 一旦找到 func 這個函數(shù)氢烘,就去執(zhí)行它的實現(xiàn)IMP .
由于每個消息都需要遍歷一次怀偷,效率會比較低。
objc_class 中另一個重要成員 objc_cache把經(jīng)常被調(diào)用的函數(shù)緩存下來播玖,大大提高函數(shù)查詢的效率椎工。把 func 的 method_name 作為 key ,method_imp 作為 value 給存起來蜀踏。
當再次收到 func 消息的時候维蒙,直接在 cache 里找到,避免去遍歷 objc_method_list
接下來看下詳細的具體流程
- ENTRY _objc_msgSend 入口
判斷接收者recevier是否為空果覆,為空則返回颅痊,不為空,就處理isa局待。
Objective-C 是一門面向?qū)ο蟮恼Z言斑响,對象又分為實例對象、類對象钳榨、元類對象以及根元類對象舰罚。它們是通過一個叫 isa 的指針來關(guān)聯(lián)起來,具體關(guān)系如下圖:
案例點擊可查看
// _objc_msgSend 入口
ENTRY _objc_msgSend
// 窗口
UNWIND _objc_msgSend, NoFrame
// tagged pointer 特殊 數(shù)據(jù)類型 數(shù)據(jù)非常小
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
建議仔細的看下這個完整的文件 相信自己能看懂 就行薛耻。官方代碼邏輯非常的清晰营罢。
首先進行緩存檢查和類型判斷
LNilOrTagged
taggedPoint:存儲小值類型,地址中包含值和類型數(shù)據(jù)饼齿,能進行快速訪問數(shù)據(jù)饲漾,提高性能
LGetIsaDone
通過匯編指令b LGetIsaDone跳轉(zhuǎn)到CacheLookup蝙搔,對緩存進行快速的查找,如果有緩存就直接返回考传,由于這一步是通過匯編執(zhí)行吃型,所以是快速查找,效率很高(這里存在查找的過程)
CacheLookup 分為三種
- CacheHit
找到了伙菊,則調(diào)用CacheHit進行call or return imp - CheckMiss
找不到 __objc_msgSend_uncached - add
別的地方找到了這imp就進行add操作败玉,方便下一次快速的查找敌土。
MethodTableLookup
如果來到這里 說明在緩存里面不存在
先找自己镜硕,如果自己沒有IMP,然后找父類的緩存,如果沒有返干,循環(huán)遞歸查找父類的IMP兴枯,一直找到NSObject,如果還是沒有矩欠,接下來就開始動態(tài)方法解析财剖,如果動態(tài)方法解析沒有實現(xiàn),接下來再調(diào)用消息轉(zhuǎn)發(fā)癌淮,流程如下,核心方法----- lookUpImpOrForward
底層源碼如下
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup 緩存中有IMP躺坟,直接返回
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
//runtimeLock 加鎖 保證線程安全(數(shù)據(jù)安全) 保證 舊數(shù)據(jù)不再重新填充
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
retry:
runtimeLock.assertLocked();
// Try this class's cache. 緩存中有IMP,直接返回
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists. 1 找自己的IMP,找到加入方法緩存
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists. 2 找父類 這一步大致與上一步相同 只是找的是 父類 上一步是自己
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.內(nèi)存溢出
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
// 沒有IMP乳蓄,調(diào)用一次resolver動態(tài)方法解析,通過triedResolver變量來控制該方法只走一次
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.消息轉(zhuǎn)發(fā)
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
消息傳遞:底層的確很復雜咪橙,涉及到寄存器位運算,下面是流程圖
流程圖
func沒有找到虚倒,通常情況下美侦,程序會在運行時掛掉并拋出 unrecognized selector sent to … 的異常。但在異常拋出前魂奥,Objective-C 的運行時會有三次拯救程序的機會菠剩。繼續(xù)看消息轉(zhuǎn)發(fā)的過程
3.消息轉(zhuǎn)發(fā)
對象在收到消息無法處理,將調(diào)用resolver解析
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// 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);
}
}
}
1.+ (BOOL)resolveInstanceMethod:(SEL)selector;
對象在收到無法解讀的消息后調(diào)用此函數(shù),參數(shù)就是那個未知的SEL(字符編碼)耻煤,其返回值為Boolean類型具壮,表示這個類是否能新增一個處理此SEL的方法。讓你有機會提供一個函數(shù)實現(xiàn)哈蝇。
如果你添加了函數(shù)并返回 YES棺妓, 那運行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程。此方法常用來實現(xiàn)@dynamic屬性买鸽、訪問CoreData框架中NSManagedObjects對象
如果 resolve 方法返回 NO 涧郊,運行時就會移到下一步:消息轉(zhuǎn)發(fā)
+(BOOL)resolveInstanceMethod:(SEL)sel{
//方法名
NSString *selStr = NSStringFromSelector(sel);
if ([selStr isEqualToString:@"name"]) {
//增加name方法的實現(xiàn)
class_addMethod(self, sel, (IMP)nameGetter, "@@:");
return YES;
}
if ([selStr isEqualToString:@"setName:"]) {
class_addMethod(self, sel, (IMP)nameSetter, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
// 或者 runtime api
IMP fooIMP = imp_implementationWithBlock(^(id _self) {
NSLog(@"Doing foo");
});
class_addMethod([self class], aSEL, fooIMP, "v@:");
2.- (id)forwardingTargetForSelector:(SEL)aSelector
接收者有第二次機會處理未知的SEL,就是把這條消息轉(zhuǎn)給其他接收者來處理眼五,這一步無法操作轉(zhuǎn)發(fā)的消息妆艘。如要修改或者處理的話就需要觸發(fā)完整的消息轉(zhuǎn)發(fā)機制
-(id)forwardingTargetForSelector:(SEL)aSelector{
NSString *selStr = NSStringFromSelector(aSelector);
//companyName彤灶,則處理轉(zhuǎn)發(fā)
if ([selStr isEqualToString:@"companyName"]) {
//返回處理這個轉(zhuǎn)發(fā)的對象
return self.companyModel;
}else{
return [super forwardingTargetForSelector:aSelector];
}
}
3.- (void)forwardInvocation:(NSInvocation *)anInvocation
這一步是 Runtime 最后一次給你挽救的機會,啟用完整的消息轉(zhuǎn)發(fā)機制批旺,創(chuàng)建NSInvocation對象:(SEL幌陕、目標及參數(shù)).
首先它會發(fā)送 -methodSignatureForSelector: 消息獲得函數(shù)的參數(shù)和返回值類型如果返回了一個函數(shù)簽名,Runtime 就會創(chuàng)建一個 NSInvocation 對象并發(fā)送 -forwardInvocation: 消息給目標對象汽煮,
觸發(fā)NSInvocation對象時搏熄,“消息派發(fā)系統(tǒng)”將親自出馬,把消息指派給目標對象.
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSMethodSignature *sig = nil;
NSString *selStr = NSStringFromSelector(aSelector);
//判斷你要轉(zhuǎn)發(fā)的SEL
if ([selStr isEqualToString:@"deptName"]) {
//此處返回的sig是方法forwardInvocation的參數(shù)anInvocation中的methodSignature
//為你的轉(zhuǎn)發(fā)方法手動生成簽名
sig = [self.companyModel methodSignatureForSelector:@selector(deptName:)];
}else{
sig = [super methodSignatureForSelector:aSelector];
}
return sig;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSString *selStr = NSStringFromSelector(anInvocation.selector);
if ([selStr isEqualToString:@"deptName"]) {
//設置處理轉(zhuǎn)發(fā)的對象
[anInvocation setTarget:self.companyModel];
//設置轉(zhuǎn)發(fā)對象要用的方法
[anInvocation setSelector:@selector(deptName:)];
BOOL hasCompanyName = YES;
//第一個和第二個參數(shù)是target和sel
[anInvocation setArgument:&hasCompanyName atIndex:2];
[anInvocation retainArguments];
[anInvocation invoke];
}else{
[super forwardInvocation:anInvocation];
}
}
細節(jié):
resolveInstanceMethod 此函數(shù)會調(diào)用倆次暇赤。
第一次:先走方法 _objc_msgSend_uncached心例,然后走方法 lookUpImpOrForward,再走方法 _class_resolveInstanceMethod鞋囊,從這個大致的流程可以知道止后,這個流程,就是上面所分析的流程溜腐,尋找 imp的過程译株,沒有找到就動態(tài)解析
第二次:消息轉(zhuǎn)發(fā)流程