Runtime消息機制:
本質上講,OC的每一次方法調度都是一次消息的發送。其中方法調度的原理如下:
/*****************************************************************
*
* id objc_msgSend(id self, SEL _cmd,...);
*
*****************************************************************/
ENTRY objc_msgSend
MESSENGER_START
cbz r0, LNilReceiver_f // 判斷消息接收者是否爲nil
ldr r9, [r0] // r9 = self->isa
CacheLookup NORMAL // 到緩存中查找方法
LCacheMiss: // 方法未緩存
MESSENGER_END_SLOW
ldr r9, [r0, #ISA]
b __objc_msgSend_uncached
LNilReceiver: // 消息接收者爲nil處理
mov r1, #0
mov r2, #0
mov r3, #0
FP_RETURN_ZERO
MESSENGER_END_NIL
bx lr
LMsgSendExit:
END_ENTRY objc_msgSend
一個方法的調度主要包括以下幾個步驟:
- 判斷接收者是否爲nil,如果爲nil,清空寄存器,消息發送返回nil
- 到類緩存中查找方法,如果存在直接返回方法
沒有找到緩存,到類的方法列表中依次尋找
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// 優先查找緩存
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
//runtimeLock在方法搜索期間保持,使方法查找+緩存填充原子相對於方法添加。否則添加一個類別,但是會無限期地忽略它,因爲在代表類別的緩存刷新之後,緩存會用舊值重新填充
runtimeLock.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
// 如果類未初始化,對其進行初始化。如果這個消息是initialize,那麼直接進行類的初始化
if (initialize && !cls->isInitialized()) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
}
retry:
runtimeLock.assertLocked();
// Try this class's cache.
// 遍歷緩存方法,如果找到,直接返回
imp = cache_getImp(cls, sel);
if (imp) goto done;
// 從當前類的方法列表中查找
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// 沿着繼承c鏈從父類的緩存查找,如果沒查找到,從父類的方法列表中查找
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// 父類緩存
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
//如果在父類方法中找到,,在當前類緩存列表中添加
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;
}
}
// 父類方法列表查找.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// 沒有找到任何的方法實現,進入消息轉發第一階段“動態方法解析”
// 調用+ (BOOL)resolveInstanceMethod: (SEL)selector
// 徵詢接收者所屬的類是否能夠動態的添加這個未實現的方法來解決問題
if (resolver && !triedResolver) {
runtimeLock.unlock();
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;
}
// 仍然沒有找到方法實現進入消息轉發第二階段
//先後會調用 -(id)forwardingTargetForSelector: (SEL)selector
// 以及 - (void)forwardInvocation: (NSInvocation*)invocation 進行最後的補救
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
- 以上爲方法調用的全部過程,主要分爲以下步驟:
- 查找是否存在對應的方法緩存,如果存在直接返回調用。爲了優化性能,方法的緩存使用了散列表的方式
- 未找到緩存,到類本身或順着類結構向上查找方法實現。如果在這個步驟中找到了方法的實現,那麼將它加入到方法緩存中以便下次調用能快速找到。如果在類自身中沒有找到方法實現,那麼循環獲取父類,重複上面的查找動作,找到後再將方法緩存到本類而非父類的緩存中
- 未找到任何方法實現,觸發消息轉發機制進行最後補救
- 其中消息轉發分爲兩個階段,第一個階段我們可以通過動態添加方法之後讓編譯器再次執行查找方法實現的過程;第二個階段稱作
備援的接收者
,就是找到一個接盤俠來處理這個事件 -
消息轉發
//本例中run爲實例發放,walk爲類方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"%@",NSStringFromSelector(sel));
if(sel == @selector(run)){
class_addMethod([self class], sel, (IMP)class_getMethodImplementation([self class], @selector(testRun)), nil);
return YES;
}
return [super resolveClassMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"%@",NSStringFromSelector(sel));
if(sel == @selector(walk)){
bool success = class_addMethod(objc_getMetaClass([NSStringFromClass(self) UTF8String]), sel, (IMP)class_getMethodImplementation(objc_getMetaClass([NSStringFromClass(self) UTF8String]), @selector(testWalk)), "v@:");
// //"v@:“,按順序分別表示:
// // v : v表示返回值爲void
// // @ :參數id(self)
// // : :SEL(_cmd)對象
return YES;
}
return [super resolveClassMethod:sel];
}
//如果沒有做上面兩個添加方法操作,可以讓其他對象來處理這個消息
- (id)forwardingTargetForSelector:(SEL)aSelector{
if([NSStringFromSelector(aSelector) isEqualToString:@"walk"]){
// return [[Tools alloc]init]; //實例方法返回
return [Tools class]; //類方法返回
}
return [super forwardingTargetForSelector:aSelector];
}
//methodSignatureForSelector需要和forwardInvocationf同時實現
//methodSignatureForSelector:和forwardInvocation:。methodSignatureForSelector:的作用在於爲另一個類實現的消息創建一個有效的方法簽名,必須實現,並且返回不爲空的methodSignature,否則會crash。
//forwardInvocation:將選擇器轉發給一個真正實現了該消息的對象。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSString *selStr = NSStringFromSelector(aSelector);
if ([selStr isEqualToString:@"run"]){
// return [NSMethodSignature signatureWithObjCTypes:nil];
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
// return [NSMethodSignature methodSignatureForSelector:(IMP)class_getMethodImplementation([self class], @selector(testRun))];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//重新定義消息接收者 改變id
// [anInvocation invokeWithTarget:[[Tools alloc] init]];
//改變消息的sel
anInvocation.selector = @selector(testRun);
[anInvocation invokeWithTarget:self];
}
緩存查找
bucket_t * cache_t::find(SEL s, id receiver)
{
assert(s != 0);
bucket_t *b = buckets();
mask_t m = mask();
//cache_hash ( return (mask_t)(uintptr_t)sel & mask; //獲取存儲在散列表中的hash下標)
mask_t begin = cache_hash(s, m);
mask_t i = begin;
do {
//如果找到則返回
if (b[i].sel() == 0 || b[i].sel() == s) {
return &b[i];
}
//hash存在衝突則繼續向下查找
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)s, cls);
}
當前類中的方法查找:
對於已排序好的列表,採用二分查找算法查找
沒有排序的列表,採用遍歷的方法查找
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
//採用二分查找
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;
}