1. 程式人生 > >iOS進階—Runtime原始碼解析:訊息傳送

iOS進階—Runtime原始碼解析:訊息傳送

GitHub參考
PS:參考GitHub分享的objc-runtime-master程式碼

1、OC中的方法呼叫,實際上objc_msgSend函式呼叫
2、objc_msgSend的執行過程大致可以分為三個部分:

  • 訊息傳送
  • 動態方法解析
  • 訊息轉發

RunTime是開源的,所以我們可以方便的檢視到RunTime的原始碼(可參考Github),底層主要運用的語言包含組合語言,C/C++

訊息傳送

objc_msgSend的相關程式碼如下:

在這裡插入圖片描述

針對不同的CPU平臺,會有不同的組合語言,所以我們只關注arm64(真機)環境下的

在這裡插入圖片描述

主要看從ENTRY _objc_msgSendEND_ENTRY _objc_msgSend

相關的組合語言,40行的程式碼

在這裡插入圖片描述

在這裡插入圖片描述

組合語言如下:我們簡單的看下

ENTRY _objc_msgSend
	UNWIND _objc_msgSend, NoFrame
	MESSENGER_START
    /// x0 recevier
    // cmp對比的意思	 訊息接收者->x0 
	cmp	x0, #0			// nil check and tagged pointer check 檢查是否為空,如果是,直接teturn
	//b->跳轉 le->小於等於 
	b.le	LNilOrTagged		//  (MSB tagged pointer looks negative)  跳轉到下面的LNilOrTagged
	//如果不為空,進入這裡 得到isa指標,x13 = isa,
	ldr	x13, [x0]		// x13 = isa
	//通過與操作,得到類   x16 = class	
	and	x16, x13, #ISA_MASK	// x16 = class	
	
//順序執行到下面
LGetIsaDone:
    //字面意思是在快取當中查詢,如果快取裡面存在,呼叫imp,否則  objc_msgSend_uncached 呼叫快取中不存在的方法
    /*
      CacheLookup 其實是定義的一個巨集,類似於 #define  CacheLookup 檢視
    */
	CacheLookup NORMAL		// calls imp or objc_msgSend_uncached

LNilOrTagged:
	b.eq	LReturnZero		// nil check

	// tagged
	mov	x10, #0xf000000000000000
	cmp	x0, x10
	b.hs	LExtTag
	adrp	x10, 
[email protected]
add x10, x10, [email protected] ubfx x11, x0, #60, #4 ldr x16, [x10, x11, LSL #3] b LGetIsaDone LExtTag: // ext tagged adrp x10, [email protected] add x10, x10, [email protected] ubfx x11, x0, #52, #8 ldr x16, [x10, x11, LSL #3] b LGetIsaDone LReturnZero: // x0 is already zero mov x1, #0 movi d0, #0 movi d1, #0 movi d2, #0 movi d3, #0 MESSENGER_END_NIL ret //相等於return END_ENTRY _objc_msgSend

定義CacheLookup的彙編程式碼部分,有兩種查詢結果,找到了和找不到,找到了->呼叫CacheHit,如果快取中找不到就呼叫CheckMiss

.macro CacheLookup
	// x1 = SEL, x16 = isa
	ldp	x10, x11, [x16, #CACHE]	// x10 = buckets, x11 = occupied|mask
	and	w12, w1, w11		// x12 = _cmd & mask
	add	x12, x10, x12, LSL #4	// x12 = buckets + ((_cmd & mask)<<4)

	ldp	x9, x17, [x12]		// {x9, x17} = *bucket
1:	cmp	x9, x1			// if (bucket->sel != _cmd)
	b.ne	2f			//     scan more
	CacheHit $0			// call or return imp
	
2:	// not hit: x12 = not-hit bucket
	CheckMiss $0			// miss if bucket->sel == 0
	cmp	x12, x10		// wrap if bucket == buckets
	b.eq	3f
	ldp	x9, x17, [x12, #-16]!	// {x9, x17} = *--bucket
	b	1b			// loop

3:	// wrap: x12 = first bucket, w11 = mask
	add	x12, x12, w11, UXTW #4	// x12 = buckets+(mask<<4)

	// Clone scanning loop to miss instead of hang when cache is corrupt.
	// The slow path may detect any corruption and halt later.

	ldp	x9, x17, [x12]		// {x9, x17} = *bucket
1:	cmp	x9, x1			// if (bucket->sel != _cmd)
	b.ne	2f			//     scan more
	CacheHit $0			// call or return imp
	
2:	// not hit: x12 = not-hit bucket
	CheckMiss $0			// miss if bucket->sel == 0
	cmp	x12, x10		// wrap if bucket == buckets
	b.eq	3f
	ldp	x9, x17, [x12, #-16]!	// {x9, x17} = *--bucket
	b	1b			// loop

3:	// double wrap
	JumpMiss $0
	
.endmacro

CheckMiss相關程式碼,如果我們的狀態屬於正常狀態NORMAL,則呼叫__objc_msgSend_uncached

.macro CheckMiss
	// miss if bucket->sel == 0
.if $0 == GETIMP
	cbz	x9, LGetImpMiss
.elseif $0 == NORMAL
	cbz	x9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
	cbz	x9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

__objc_msgSend_uncached以後的相關程式碼

//入口
STATIC_ENTRY __objc_msgSend_uncached
	UNWIND __objc_msgSend_uncached, FrameWithNoSaves

	// THIS IS NOT A CALLABLE C FUNCTION
	// Out-of-band x16 is the class to search
	
	//如果在上面的快取中沒有找到,就執行下面的,在方法列表中去尋找
	MethodTableLookup
	br	x17

	END_ENTRY __objc_msgSend_uncached


	STATIC_ENTRY __objc_msgLookup_uncached
	UNWIND __objc_msgLookup_uncached, FrameWithNoSaves

	// THIS IS NOT A CALLABLE C FUNCTION
	// Out-of-band x16 is the class to search
	
	MethodTableLookup
	ret

	END_ENTRY __objc_msgLookup_uncached


	STATIC_ENTRY _cache_getImp

	and	x16, x0, #ISA_MASK
	CacheLookup GETIMP

MethodTableLookup相關程式碼

.macro MethodTableLookup
	
	// push frame
	stp	fp, lr, [sp, #-16]!
	mov	fp, sp

	// save parameter registers: x0..x8, q0..q7
	sub	sp, sp, #(10*8 + 8*16)
	stp	q0, q1, [sp, #(0*16)]
	stp	q2, q3, [sp, #(2*16)]
	stp	q4, q5, [sp, #(4*16)]
	stp	q6, q7, [sp, #(6*16)]
	stp	x0, x1, [sp, #(8*16+0*8)]
	stp	x2, x3, [sp, #(8*16+2*8)]
	stp	x4, x5, [sp, #(8*16+4*8)]
	stp	x6, x7, [sp, #(8*16+6*8)]
	str	x8,     [sp, #(8*16+8*8)]

	// receiver and selector already in x0 and x1
	//接收器和選擇器已經在x0和x1中,跳轉 呼叫 __class_lookupMethodAndLoadCache3
	mov	x2, x16
	bl	__class_lookupMethodAndLoadCache3

	// imp in x0
	mov	x17, x0
	
	// restore registers and return
	ldp	q0, q1, [sp, #(0*16)]
	ldp	q2, q3, [sp, #(2*16)]
	ldp	q4, q5, [sp, #(4*16)]
	ldp	q6, q7, [sp, #(6*16)]
	ldp	x0, x1, [sp, #(8*16+0*8)]
	ldp	x2, x3, [sp, #(8*16+2*8)]
	ldp	x4, x5, [sp, #(8*16+4*8)]
	ldp	x6, x7, [sp, #(8*16+6*8)]
	ldr	x8,     [sp, #(8*16+8*8)]

	mov	sp, fp
	ldp	fp, lr, [sp], #16

__class_lookupMethodAndLoadCache3 就是C/C++實現的,彙編過程結束

C/C++與組合語言有一個約定,C/C++中的方法會在彙編中多一個下劃線,所以我們接下來搜尋_class_lookupMethodAndLoadCache3

在這裡插入圖片描述

我們檢視新版本

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    //cls 類 sel 方法名 obj 物件 YES 是否初始化 NO 是否在快取中 YES 是否嘗試動態解析
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

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
    //在彙編程式中,已經查詢過快取了,在這裡仍要查詢的原因是因為,OC是動態呼叫方法,防止動態呼叫時直接呼叫此方法
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    // 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.read();

    if (!cls->isRealized()) {
        // Drop the read-lock and acquire the write-lock.
        // realizeClass() checks isRealized() again to prevent
        // a race while the lock is down.
        runtimeLock.unlockRead();
        runtimeLock.write();

        realizeClass(cls);

        runtimeLock.unlockWrite();
        runtimeLock.read();
    }

    if (initialize  &&  !cls->isInitialized()) {
        runtimeLock.unlockRead();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.read();
        // 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.assertReading();

    // Try this class's cache.

    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    // Try this class's method lists.
    {
    //在此類方法列表中查詢,如果查詢不到就去七父類方法中查詢,直到找到為止,執行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;
        }
    }

    // Try superclass caches and method lists.
    {
        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.");
            }
            
            // 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.

    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // 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.

    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();

    return imp;
}

官方大致圖解如下:

在這裡插入圖片描述