iOS底層原理 runtime- objc_msgSend拾遺基礎篇--(7)

上篇咱們講過了iOS底層原理 runtime-object_class拾遺基礎,arm64以後isa是使用聯合體使用更少的空間存儲更多的數據,以及如何自定義和使用聯合體,objc_class->cache_t cache是一個是緩存最近調用class的方法,當緩存剩餘空間小余1/4則進行擴容,擴容爲原來的兩倍,擴容以後,已存儲的method_t擴容以後以後被清空。今天咱們在瞭解runtime的消息轉發機制。c++

基礎知識

OC中的方法調用,其實都是轉換爲objc_msgSend函數的調用git

objc_msgSend的執行流程能夠分爲3大階段github

  1. 消息發送
  2. 動態方法解析
  3. 消息轉發

那麼咱們根據這三塊內容進行源碼解讀。源碼執行的順序大概以下數組

objc-msg-arm64.s
ENTRY _objc_msgSend
b.le	LNilOrTagged //<0則返回
CacheLookup NORMAL //緩存查找 未命中則繼續查找
.macro CacheLookup// 經過宏 查找cache,命中直接call or return imp
.macro CheckMiss //miss 則跳轉__objc_msgSend_uncached
STATIC_ENTRY __objc_msgSend_uncached 
.macro MethodTableLookup//方法中查找
__class_lookupMethodAndLoadCache3//跳轉->__class_lookupMethodAndLoadCache3 在runtime-class-new.mm 4856行


objc-runtime-new.mm
_class_lookupMethodAndLoadCache3
lookUpImpOrForward
getMethodNoSuper_nolock、search_method_list、log_and_fill_cache
cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache
_class_resolveInstanceMethod
_objc_msgForward_impcache


objc-msg-arm64.s
STATIC_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward

Core Foundation
__forwarding__(不開源)
複製代碼

消息發送

objc_msgSend是彙編寫的,在源碼objc-msg-arm64.s304行,是objc_msgSend的開始,_objc_msgSend結束是351行, 進入到objc_msgSend函數內部一探究竟:緩存

ENTRY _objc_msgSend // _objc_msgSend 開始
	UNWIND _objc_msgSend, NoFrame

	cmp	p0, #0 // 檢查p0寄存器是不是0 _objc_msgSend()第一個參數:self
#if SUPPORT_TAGGED_POINTERS
	b.le	LNilOrTagged		// if le < 0 ->  跳轉到標籤  LNilOrTagged
#else
	b.eq	LReturnZero // if le == 0 ->  跳轉到標籤  LReturnZero
#endif
	ldr	p13, [x0]		// p13 = isa
	GetClassFromIsa_p16 p13		// p16 = class
LGetIsaDone:
	CacheLookup NORMAL		// calls imp or objc_msgSend_uncached

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
	b.eq	LReturnZero		// 若是==0 -> LReturnZero

	// tagged
	adrp	x10, _objc_debug_taggedpointer_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
	ubfx	x11, x0, #60, #4
	ldr	x16, [x10, x11, LSL #3]
	adrp	x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
	add	x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
	cmp	x10, x16
	b.ne	LGetIsaDone

	// ext tagged
	adrp	x10, _objc_debug_taggedpointer_ext_classes@PAGE
	add	x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
	ubfx	x11, x0, #52, #8
	ldr	x16, [x10, x11, LSL #3]
	b	LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif

LReturnZero:
	// x0 is already zero
	mov	x1, #0
	movi	d0, #0
	movi	d1, #0
	movi	d2, #0
	movi	d3, #0
	ret //return 返回結束掉

	END_ENTRY _objc_msgSend // _objc_msgSend 結束
複製代碼

objc_msgSend(id,SEL,arg)id爲空的時候,跳轉標籤LNilOrTagged,進入標籤內,當等於0則跳轉LReturnZero,進入到LReturnZero內,清除數據和return。不等於零,獲取isa和class,調用CacheLookup NORMAL,進入到CacheLookup內部bash

.macro CacheLookup //.macro 是一個宏 使用 _cmd&mask 查找緩存中的方法
	// p1 = SEL, p16 = isa
	ldp	p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
#if !__LP64__
	and	w11, w11, 0xffff	// p11 = mask
#endif
	and	w12, w1, w11		// x12 = _cmd & mask
	add	p12, p10, p12, LSL #(1+PTRSHIFT)
		             // p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))

	ldp	p17, p9, [x12]		// {imp, sel} = *bucket
1:	cmp	p9, p1			// if (bucket->sel != _cmd)
	b.ne	2f			//     scan more
	CacheHit $0			// call or return imp 命中 調用或者返回imp
	
2:	// not hit: p12 = not-hit bucket 沒有命中
	CheckMiss $0			// miss if bucket->sel == 0
	cmp	p12, p10		// wrap if bucket == buckets
	b.eq	3f
	ldp	p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
	b	1b			// loop

3:	// wrap: p12 = first bucket, w11 = mask
	add	p12, p12, w11, UXTW #(1+PTRSHIFT)
		                        // p12 = buckets + (mask << 1+PTRSHIFT)

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

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

3:	// double wrap
	JumpMiss $0
	
.endmacro
複製代碼

彙編代碼左邊是代碼,右邊是註釋,大概均可以看懂的。 當命中則return imp,不然則跳轉CheckMiss,進入到CheckMiss內部:數據結構

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

剛纔傳的值是NORMAL,則跳轉__objc_msgSend_uncached,進入到__objc_msgSend_uncached內部(484行):架構

STATIC_ENTRY __objc_msgSend_uncached
	UNWIND __objc_msgSend_uncached, FrameWithNoSaves
	MethodTableLookup
	TailCallFunctionPointer x17
	END_ENTRY __objc_msgSend_uncached
複製代碼

調用MethodTableLookup,咱們查看MethodTableLookup內部:app

.macro MethodTableLookup
	// push frame
	SignLR
	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
	mov	x2, x16
	bl	__class_lookupMethodAndLoadCache3//跳轉->__class_lookupMethodAndLoadCache3 在runtime-class-new.mm 4856行

	// 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
	AuthenticateLR
.endmacro

複製代碼

最終跳轉到__class_lookupMethodAndLoadCache3,去掉一個下劃線就是c函數,在runtime-class-new.mm 4856行, 調用了函數lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/);,第一次會初始化clsresolver的值, 中最終跳轉到c/c++函數lookUpImpOrForward,該函數是最終能看到的c/c++,如今咱們進入到lookUpImpOrForward內部查看:iphone

/***********************************************************************
* lookUpImpOrForward.
* initialize==NO 儘可能避免調用,有時可能也會調用。
* cache==NO 跳過緩存查找,其餘地方可能會不調過
* 大多數人會傳值 initialize==YES and cache==YES
*   若是cls是非初始化的元類,則非Non-nil會快點
* May return _objc_msgForward_impcache. IMPs destined for external use 
*   must be converted to _objc_msgForward or _objc_msgForward_stret.
* 若是你不想用forwarding,則調用lookUpImpOrNil()代替
**********************************************************************/
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
    if (cache) { //從彙編過來是NO
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    runtimeLock.lock();
    checkIsKnownClass(cls);

    if (!cls->isRealized()) {
        realizeClass(cls);
    }

    if (initialize  &&  !cls->isInitialized()) {
		//當cls須要初始化和沒有初始化的時候 進行cls初始化,
		//初始化會加入到一個線程,同步執行,先初始化父類,再初始化子類
		//數據的大小最小是4,擴容規則是:n*2+1;
        runtimeLock.unlock();
        _class_initialize (_class_getNonMetaClass(cls, inst));
        runtimeLock.lock();
    }

    
 retry:    
    runtimeLock.assertLocked();

//再次獲取imp
    imp = cache_getImp(cls, sel);
    if (imp) goto done;

    //嘗試在本類中查找method
    {//從cls->data()->methods查找method
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {//找到添加到cache中
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }

    // Try superclass caches and method lists.
	//從cls->superclass->data()->methods查找methd,supercls沒有查找出來,再查找父類的父類。
    {
        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; } } } //若是尚未找到imp,進入動態方法解析階段 if (resolver && !triedResolver) { runtimeLock.unlock(); _class_resolveMethod(cls, sel, inst); runtimeLock.lock(); triedResolver = YES; goto retry; } //若是沒找到resolveInstanceMethod 和resolveClassMethod, // 進行消息轉發 階段 imp = (IMP)_objc_msgForward_impcache; //填充 cache cache_fill(cls, sel, imp, inst); done: runtimeLock.unlock(); return imp; } 複製代碼

SUPPORT_INDEXED_ISA是在arm64LP64 還有arm_arch_7k>2爲1,iphone屬於arm64mac os屬於LP64,因此SUPPORT_INDEXED_ISA = 1.

// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa 
// field as an index into a class table.
// Note, keep this in sync with any .s files which also define it.
// Be sure to edit objc-abi.h as well.
// __ARM_ARCH_7K__ 處理器架構指令集版本
//__arm64__ 架構
//__LP64__ uinx 和uinx  mac os
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
複製代碼

lookUpImpOrForward函數的 大概思路以下:

首次已經從緩存中查過沒有命中因此再也不去緩存中查了,而後判斷cls是否已經實現,cls->isRealized(),沒有實現的話進行實現realizeClass(cls),主要是將初始化read-write data和其餘的一些數據,後續會細講。而後進行cls的初始化_class_initialize(),當cls須要初始化和沒有初始化的時候進行cls初始化,初始化會加入到一個線程,同步執行,先初始化父類,再初始化子類,數據的大小最小是4,擴容規則是:n*2+1;而後再次獲取impcache_getImp,而後在cls方法中查找該method,而後就是在superclass中查找方法,直到父類是nil,找到的話,獲取imp並將clssel加入到cache中,不然進入到消息解析階段_class_resolveMethod,在轉發階段,不是元類的話,進入到_class_resolveInstanceMethod是元類的話調用_class_resolveClassMethod,這兩種分別都會進入到lookUpImpOrNil,再次查找IMP,當沒找到的話就返回,找到的話用objc_msgSend發送消息實現調用SEL_resolveInstanceMethod並標記triedResolver爲已動態解析標誌。而後進入到消息動態轉發階段_objc_msgForward_impcache,至此runtime發送消息結束。

借用網上找一個圖, 能夠更直觀的看出流程運轉。

realizeClass()解析

realizeClass是初始化了不少數據,包括cls->ro賦值給cls->rw,添加元類version爲7,cls->chooseClassArrayIndex()設置cls的索引,supercls = realizeClass(remapClass(cls->superclass)); metacls = realizeClass(remapClass(cls->ISA()))初始化superclasscls->isa,後邊針對沒有優化的結構進行賦值這裏很少講,而後協調實例變量偏移佈局,設置cls->setInstanceSize,拷貝flagsrorw中,而後添加subclassrootclass,最後添加類別的方法,協議,和屬性。

/***********************************************************************
* realizeClass
 cls第一次初始化會執行,包括cls->rw->data(),返回真實的cls 結構體
 runtimelock 必須有調用者把寫入鎖鎖起來
**********************************************************************/
static Class realizeClass(Class cls)
{
    runtimeLock.assertLocked();

    const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    assert(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?
//首先將tw賦值給to,由於數據結構同樣能夠直接強制轉化
    ro = (const class_ro_t *)cls->data();
    if (ro->flags & RO_FUTURE) {//是否已經初始化過,初始化過的哈 則 cls->rw 已經初始化過
        rw = cls->data();
        ro = cls->data()->ro;
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // 正常狀況下 申請class_rw_t空間
        rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
        rw->ro = ro;//cls->rw->ro 指向如今的ro
        rw->flags = RW_REALIZED|RW_REALIZING;//realized = 1 and  realizing = 1
        cls->setData(rw);//賦值
    }

    isMeta = ro->flags & RO_META;//是不是元類
	

    rw->version = isMeta ? 7 : 0;  // 元類版本是7,舊版的6,否就是0


    // Choose an index for this class.
//設置cls的索引
	cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u", 
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex());
    }

    // 若是父類沒有初始化則進行初始化
    // root_class 作完須要設置RW_REALIZED=1,
    // root metaclasses 須要執行完.
	//從NXMapTable 獲取cls ,而後進行初始化
	//從NXMapTable 獲取cls->isa ,而後進行初始化
    supercls = realizeClass(remapClass(cls->superclass));
    metacls = realizeClass(remapClass(cls->ISA()));
//沒有通過優化的isa執行的,如今已是version=7,在arm64上是優化過的,這個先不看了。
#if SUPPORT_NONPOINTER_ISA
    // Disable non-pointer isa for some classes and/or platforms.
    // Set instancesRequireRawIsa.
    bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
    bool rawIsaIsInherited = false;
    static bool hackedDispatch = false;

    if (DisableNonpointerIsa) {
        // Non-pointer isa disabled by environment or app SDK version
        instancesRequireRawIsa = true;
    }
    else if (!hackedDispatch  &&  !(ro->flags & RO_META)  &&  
             0 == strcmp(ro->name, "OS_object")) 
    {
        // hack for libdispatch et al - isa also acts as vtable pointer
        hackedDispatch = true;
        instancesRequireRawIsa = true;
    }
    else if (supercls  &&  supercls->superclass  &&  
             supercls->instancesRequireRawIsa()) 
    {
        // This is also propagated by addSubclass() 
        // but nonpointer isa setup needs it earlier.
        // Special case: instancesRequireRawIsa does not propagate 
        // from root class to root metaclass
        instancesRequireRawIsa = true;
        rawIsaIsInherited = true;
    }
    
    if (instancesRequireRawIsa) {
        cls->setInstancesRequireRawIsa(rawIsaIsInherited);
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // Update superclass and metaclass in case of remapping
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

	// 協調實例變量偏移/佈局
	//可能從新申請空間 class_ro_t,更新咱們的class_ro_t
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // 設置setInstanceSize 從ro->instanceSize
    cls->setInstanceSize(ro->instanceSize);

	//拷貝flags 從ro到rw中
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
//添加superclass指針
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
	//類別的方法 在編譯的時候沒有添加到二進制文件中,在運行的時候添加進去的
    methodizeClass(cls);

    return cls;
}
複製代碼

這裏最後添加類別的數據是調用了methodizeClass函數,這個函數首先添加method_list_t *list = ro->baseMethods()rw->methods.attachLists(&list, 1),而後將屬性property_list_t *proplist=ro->baseProperties添加到rw->properties.attachLists(&proplist, 1),最後將協議列表protocol_list_t *protolist = ro->baseProtocols追加到rw->protocols.attachLists(&protolist, 1),若是是metaclass則添加SEL_initialize,而後從全局NXMapTable *category_map刪除已經加載的category_list,最後調用attachCategories(cls, cats, false /*don't flush caches*/)將已經加載的cats的方法添加到cls->rw上面而且不刷新caches

/***********************************************************************
* methodizeClass
 修復cls方法列表想,協議列表和屬性列表
* 加鎖
**********************************************************************/
static void methodizeClass(Class cls)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro;

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

	//方法列表
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
	//將對象的方法追加到cls->rw->methods後面
        rw->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
	//將對象的屬性追加到rw->properties後面
        rw->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
	//將對象的協議追加到rw->protocols後面
        rw->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have // them already. These apply before category replacements. if (cls->isRootMetaclass()) { // root metaclass addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO); } // Attach categories. //類別 從全局NXMapTable *category_map 已經加載過了。 category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/); //收集全部的cats到cls -> rw中 attachCategories(cls, cats, false /*don't flush caches*/);

    if (PrintConnecting) {
        if (cats) {
            for (uint32_t i = 0; i < cats->count; i++) {
                _objc_inform("CLASS: attached category %c%s(%s)", 
                             isMeta ? '+' : '-', 
                             cls->nameForLogging(), cats->list[i].cat->name);
            }
        }
    }
    
    if (cats) free(cats);//釋放cats

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
    for (const auto& meth : rw->methods) {
        if (PrintConnecting) {
            _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(meth.name));
        }
        assert(sel_registerName(sel_getName(meth.name)) == meth.name); 
    }
#endif
}
複製代碼

attachCategories()解析

methodizeClass以前rw初始化的時候並無將其餘數據都都複製給rw,如今methodizeClass實現了將原本的ro數據拷貝給rw,而後attachCategories將 分類的方法,屬性,協議追加到cls->data->rw,咱們進入attachCategories內部

static void attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
	//方法數組[[1,2,3],[4,5,6],[7,8,9]]
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
	//屬性數組
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
	//協議數組
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {
		//取出某個分類
        auto& entry = cats->list[i];
//取出分類 的 instance方法或者class方法
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist; //mlists 接受全部分類方法
            fromBundle |= entry.hi->isBundle();
        }
//proplist 接受全部分類屬性
        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }
//proplist 接受全部協議方法
        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }
//收集了全部協議 分類方法
    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
	//追加全部分類方法
    rw->methods.attachLists(mlists, mcount);
	//釋放數組
    free(mlists);
	//刷新該類的緩存
    if (flush_caches  &&  mcount > 0) flushCaches(cls);
//追加全部分類屬性
    rw->properties.attachLists(proplists, propcount);
    free(proplists);//釋放數組
//追加全部分類協議
    rw->protocols.attachLists(protolists, protocount);
    free(protolists);//釋放數組
}
複製代碼

rw->list->attachLists()解析

添加attachLists函數規則是後來的卻添加到內存的前部分,這裏就清楚爲何後編譯類別能後邊的類別覆蓋前邊的類別的相同名字的方法。

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
			//一共須要的數量
            uint32_t newCount = oldCount + addedCount;
			//分配內存 內存不夠用了,須要擴容
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
			//賦值count
            array()->count = newCount;
			// array()->lists:原來的方法列表向後移動 oldCount * sizeof(array()->lists[0]個長度
            memmove(array()->lists + addedCount/*數組末尾*/, array()->lists/*數組*/,
                    oldCount * sizeof(array()->lists[0])/*移動的大小*/);
			//空出來的 內存使用addedLists拷貝過去 大小是:addedCount * sizeof(array()->lists[0])
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
			/*
			圖示講解:
			array()->lists:A->B->C->D->E
		addedCount:3
		addedLists:P->L->V
			memmove以後:nil->nil->nil->A->B->C->D->E
			而後再講addedLists插入到數組前邊,最終array()->lists的值是:
			P->L->V->A->B->C->D->E
			 */
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }
複製代碼

class初始化完成了,而後再次嘗試獲取imp = cache_getImp,因爲緩存沒有中間也沒添加進去,因此這裏也是空的,而後從getMethodNoSuper_nolock獲取該cls的方法列表中查找,沒有的話再從superclass查找cachemethod,找到的話,進行log_and_fill_cache至此消息發送完成。

消息動態解析

動態解析函數_class_resolveMethod(cls, sel, inst),若是不是元類調用_class_resolveInstanceMethod,若是是的話調用_class_resolveClassMethod

/***********************************************************************
* _class_resolveMethod
* 調用 +resolveClassMethod 或者 +resolveInstanceMethod
* 若是存在了則不檢查
**********************************************************************/
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {//不是元類則調用 實例的
	//首先調用
		_class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
		//尋找classMethod
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}
複製代碼

resolveInstanceMethod,查找SEL_resolveInstanceMethod,傳值不用初始化,不用消息解析,可是cache要查找。沒有找到的直接返回,找到的話使用objc_msgSend發送消息調用SEL_resolveInstanceMethod

/***********************************************************************
* _class_resolveInstanceMethod
* 調用 class添加的函數 +resolveInstanceMethod
* 有多是元類
* 若是方法存在則不檢查
**********************************************************************/
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
    if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }
//若是找到SEL_resolveInstanceMethod 則使用objc_msgSend函數
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveInstanceMethod adds to self a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
複製代碼

_class_resolveClassMethod中,第一步先去lookUpImpOrNil查找+SEL_resolveClassMethod方法,沒找到的就結束,找到則調用objc_msgsend(id,sel)

static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
    assert(cls->isMetaClass());

    if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst, 
                         NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
    {
        // Resolver not implemented.
        return;
    }

    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(_class_getNonMetaClass(cls, inst), 
                        SEL_resolveClassMethod, sel);

    // Cache the result (good or bad) so the resolver doesn't fire next time. // +resolveClassMethod adds to self->ISA() a.k.a. cls IMP imp = lookUpImpOrNil(cls, sel, inst, NO/*initialize*/, YES/*cache*/, NO/*resolver*/); if (resolved && PrintResolving) { if (imp) { _objc_inform("RESOLVE: method %c[%s %s] " "dynamically resolved to %p", cls->isMetaClass() ? '+' : '-', cls->nameForLogging(), sel_getName(sel), imp); } else { // Method resolver didn't add anything?
            _objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
                         ", but no new implementation of %c[%s %s] was found",
                         cls->nameForLogging(), sel_getName(sel), 
                         cls->isMetaClass() ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(sel));
        }
    }
}
複製代碼

動態解析至此完成。

消息轉發

_objc_msgForward_impcache是轉發的函數地址,在搜索框搜索發現,這個函數除了.s文件中有,其餘地方均只是調用,說明這個函數是彙編實現,在objc-msg-arm64.s 531 行發現一點蹤影

STATIC_ENTRY __objc_msgForward_impcache //開始__objc_msgForward_impcache
	// No stret specialization.
	b	__objc_msgForward//跳轉->__objc_msgForward
	END_ENTRY __objc_msgForward_impcache // 結束__objc_msgForward_impcache

	
	ENTRY __objc_msgForward // 開始 __objc_msgForward

	adrp	x17, __objc_forward_handler@PAGE
	ldr	p17, [x17, __objc_forward_handler@PAGEOFF]//p17= x17 和 __objc_forward_handler@PAGEOFF的和
	TailCallFunctionPointer x17 //跳轉-> TailCallFunctionPointer

	END_ENTRY __objc_msgForward//結束 __objc_msgForward
複製代碼

當跳轉到adrp x17, __objc_forward_handler@PAGE這一行,搜搜索函數_objc_forward_handler,看到只是個打印函數,並無其餘函數來替代這個指針,那麼咱們用其餘方法來探究。

__attribute__((noreturn)) 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);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
複製代碼

網上有大神總結的點咱們先參考下

// 僞代碼
int __forwarding__(void *frameStackPointer, int isStret) {
    id receiver = *(id *)frameStackPointer;
    SEL sel = *(SEL *)(frameStackPointer + 8);
    const char *selName = sel_getName(sel);
    Class receiverClass = object_getClass(receiver);

    // 調用 forwardingTargetForSelector:
    if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
        id forwardingTarget = [receiver forwardingTargetForSelector:sel];
        if (forwardingTarget && forwardingTarget != receiver) {
            if (isStret == 1) {
                int ret;
                objc_msgSend_stret(&ret,forwardingTarget, sel, ...);
                return ret;
            }
            return objc_msgSend(forwardingTarget, sel, ...);
        }
    }

    // 殭屍對象
    const char *className = class_getName(receiverClass);
    const char *zombiePrefix = "_NSZombie_";
    size_t prefixLen = strlen(zombiePrefix); // 0xa
    if (strncmp(className, zombiePrefix, prefixLen) == 0) {
        CFLog(kCFLogLevelError,
              @"*** -[%s %s]: message sent to deallocated instance %p",
              className + prefixLen,
              selName,
              receiver);
        <breakpoint-interrupt>
    }

    // 調用 methodSignatureForSelector 獲取方法簽名後再調用 forwardInvocation
    if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
        NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
        if (methodSignature) {
            BOOL signatureIsStret = [methodSignature _frameDescriptor]->returnArgInfo.flags.isStruct;
            if (signatureIsStret != isStret) {
                CFLog(kCFLogLevelWarning ,
                      @"*** NSForwarding: warning: method signature and compiler disagree on struct-return-edness of '%s'. Signature thinks it does%s return a struct, and compiler thinks it does%s.",
                      selName,
                      signatureIsStret ? "" : not,
                      isStret ? "" : not);
            }
            if (class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
                NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];

                [receiver forwardInvocation:invocation];

                void *returnValue = NULL;
                [invocation getReturnValue:&value];
                return returnValue;
            } else {
                CFLog(kCFLogLevelWarning ,
                      @"*** NSForwarding: warning: object %p of class '%s' does not implement forwardInvocation: -- dropping message",
                      receiver,
                      className);
                return 0;
            }
        }
    }

    SEL *registeredSel = sel_getUid(selName);

    // selector 是否已經在 Runtime 註冊過
    if (sel != registeredSel) {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: selector (%p) for message '%s' does not match selector known to Objective C runtime (%p)-- abort",
              sel,
              selName,
              registeredSel);
    } // doesNotRecognizeSelector
    else if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
        [receiver doesNotRecognizeSelector:sel];
    }
    else {
        CFLog(kCFLogLevelWarning ,
              @"*** NSForwarding: warning: object %p of class '%s' does not implement doesNotRecognizeSelector: -- abort",
              receiver,
              className);
    }

    // The point of no return.
    kill(getpid(), 9);
}
複製代碼

驗證動態解析

咱們簡單定義一個test函數,而後並執行這個函數。

@interface Person : NSObject
- (void)test;
@end
@implementation Person
+(BOOL)resolveInstanceMethod:(SEL)sel{
	NSLog(@"%s",__func__);
	if (sel == @selector(test)) {
		Method me = class_getInstanceMethod(self, @selector(test2));
		class_addMethod(self, sel,
						method_getImplementation(me),
						method_getTypeEncoding(me));
		return YES;
	}
	return [super resolveInstanceMethod:sel];
}
-(void)test2{
	NSLog(@"來了,老弟");
}
@end

Person *p = [[Person alloc]init];
[p test];
[p test];
 //輸出
+[FYPerson resolveInstanceMethod:]
 -[FYPerson test3]
 -[FYPerson test3]
複製代碼

[p test]在第一次執行的時候會走到消息動態解析的這一步,而後經過objc_msgsend調用了test,而且把test添加到了緩存中,因此輸出了+[FYPerson resolveInstanceMethod:],在第二次調用的時候,會從緩存中查到imp,因此直接輸出了-[FYPerson test3]

+resolveInstanceMethod能夠攔截掉實例方法的動態解析,在+resolveClassMethod能夠攔截類方法。

@interface Person : NSObject
+ (void)test;
@end

+ (void)test3{
	NSLog(@"來了,老弟");
}
+ (BOOL)resolveClassMethod:(SEL)sel{
	NSLog(@"%s",__func__);
	if (sel == @selector(test)) {
		Method me = class_getClassMethod(self, @selector(test3));//獲取method
		//給sel 添加方法實現 @selecter(test3)
		class_addMethod(object_getClass(self), sel,
						method_getImplementation(me),
						method_getTypeEncoding(me));
		return YES;
	}
	return [super resolveInstanceMethod:sel];
}

[Person test];

//輸出
+[Person resolveClassMethod:]
來了,老弟
複製代碼

攔截+resolveClassMethod,在條件爲sel==@selector(test)的時候,將函數實現+test3()IMP使用class_addMethod添加到Person上,待下次調用test的時候直接經過imp = cache_getImp(cls, sel);獲取到imp函數指針而且執行。 咱們也能夠經過添加c函數的imp來實現給class添加函數實現。

+(BOOL)resolveInstanceMethod:(SEL)sel{
    NSLog(@"%s",__func__);
    if (sel == @selector(test)) {
//        Method me = class_getInstanceMethod(self, @selector(test3));
//        class_addMethod(self.class, sel, method_getImplementation(me), method_getTypeEncoding(me));
        class_addMethod(self.class, sel, (IMP)test3, "v16@0:8");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
void test3(id self,SEL sel){
    NSLog(@"test3:%s",NSStringFromSelector(sel).UTF8String);
}

//輸出
+[FYPerson resolveInstanceMethod:]
test3:test
test3:test
複製代碼

v16@0:8是返回值爲void參數佔用16字節大小,第一個是從0開始,第二個從8字節開始。 這段代碼和上面的其實本質上是同樣的,一個是給class添加函數實現,使selimp對應起來,這個是將c函數的impsel進行關聯,添加緩存以後,使用objc_msgsend()效果是同樣的。

驗證消息轉發

消息轉發可分爲3步,第一步根據- (id)forwardingTargetForSelector:(SEL)aSelector返回的類對象或者元類對象,將方法轉發給該對象。假如第一步沒實現,則第二步根據返回的-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector函數簽名,在第三步(void)forwardInvocation:(NSInvocation *)anInvocation調用函數[anInvocation invoke]進行校驗成功以後進行調用函數。

@interface Person : NSObject
- (void)test;
@end

#import "Person.h"
#import "Student.h"

@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector{
	if (aSelector == @selector(test)) {
		//objc_msgSend([[Struent alloc]init],test)
		return [[Struent alloc]init];
	}
	return [super forwardingTargetForSelector:aSelector];
}
@end
//輸出
-[Student test]
複製代碼

咱們定義了一個Person只聲明瞭test沒有實現,而後在消息轉發第一步forwardingTargetForSelector將要處理的對象返回,成功調用了Studenttest方法。

第一步沒攔截,能夠在第二步攔截。

//消息轉發第二步 沒有對象來處理方法,那將函數簽名來實現
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
	if (aSelector == @selector(test)) {
		NSMethodSignature *sign = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
		return sign;
	}
	return [super methodSignatureForSelector:aSelector];
}
// 函數簽名已返回,到了函數調用的地方
//selector 函數的sel
//target   函數調用者
//methodSignature 函數簽名
//NSInvocation  封裝數據的對象
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    NSLog(@"%s",__func__);
}
//輸出
-[Person forwardInvocation:]
複製代碼

打印出了-[Person forwardInvocation:]並且沒有崩潰,在forwardInvocation:(NSInvocation *)anInvocation怎麼操做看開發者怎麼處理了,探究下均可以作什麼事情。 看到NSInvocation的屬性和函數,seltarget是讀寫,函數簽名是必須的,因此(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector必須將函數簽名返回。

@property (readonly, retain) NSMethodSignature *methodSignature;//只讀
- (void)retainArguments;
@property (readonly) BOOL argumentsRetained;
@property (nullable, assign) id target;//讀寫
@property SEL selector;//讀寫
複製代碼

當攔截方法是類方法的時候,能夠用+ (id)forwardingTargetForSelector:(SEL)aSelecto攔截,

//class 轉發
// 消息轉發第一步 攔截是否有轉發的class對象處理方法
+ (id)forwardingTargetForSelector:(SEL)aSelector{
	if (aSelector == @selector(test3)) {
		//objc_msgSend([[Struent alloc]init],test)
		return [Student class];
	}
	return [super forwardingTargetForSelector:aSelector];
}

+ (void)test3{
//	NSLog(@"+[Student test3]");
//當[Person test3]上一行寫這麼一行,Person *p = [[Person alloc]init] 這句報錯
//暫時不懂爲何不能調用NSLog,可是已經進來了因此調用了test2。
//註釋掉 [[Person alloc]init],一切正常。 有大佬瞭解嗎
}
- (void)test2{
	NSLog(@"%s",__func__);
}

// 輸出
-[Student test2]
複製代碼

也能夠用返回return [[Student alloc]init];class類方法轉化成實例方法,最後調用了Student的對象方法test3。其實本質上都是objc_msgSend(id,SEL,...),咱們修改的只是id的值,id類型在這段代碼中本質是對象,因此咱們能夠return instance也能夠reurn class

+ (id)forwardingTargetForSelector:(SEL)aSelector{
	if (aSelector == @selector(test3)) {
		//objc_msgSend([[Struent alloc]init],test)
		return [[Student alloc]init];
	}
	return [super forwardingTargetForSelector:aSelector];
}

- (void)test3{
	NSLog(@"%s",__func__);
}
//輸出
-[Student test3]
複製代碼

將剛纔寫的methodSignatureForSelectorforwardInvocation改爲類方法,也是一樣能夠攔截類方法的。咱們看下

//消息轉發第二步 沒有class來處理方法,那將函數簽名來實現
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
	if (aSelector == @selector(test3)) {
		NSMethodSignature *sign = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
		return sign;
	}
	return [super methodSignatureForSelector:aSelector];
}
// 函數簽名已返回,到了函數調用的地方
//selector 函數的sel
//target   函數調用者
//methodSignature 函數簽名
//NSInvocation  封裝數據的對象
+ (void)forwardInvocation:(NSInvocation *)anInvocation{
//	anInvocation.selector = @selector(test2);
//此處換成[Student class]一樣能夠
//	anInvocation.target = (id)[[Student alloc]init];

//	[anInvocation invoke];
	NSLog(@"%s",__func__);
}

//輸出
+[Person forwardInvocation:]
複製代碼

測過其實對象方法和類方法都是用一樣的流程攔截的,對象方法是用-方法,類方法是用+方法。

總結

  • objc_msgSend發送消息,會首先在cache中查找,查找不到則去方法列表(順序是cache->class_rw_t->supclass cache ->superclass class_rw_t ->動態解析)
  • 第二步是動態解析,能在resolveInstanceMethod或+ (BOOL)resolveClassMethod:(SEL)sel來來攔截,能夠給class新增實現函數,達到不崩潰目的
  • 第三步是消息轉發,轉發第一步能夠在+ (id)forwardingTargetForSelector:(SEL)aSelector- (id)forwardingTargetForSelector:(SEL)aSelector攔截類或實例方法,能將對象方法轉發給其餘對象,也能將對象方法轉發給類方法,也能夠將類方法轉發給實例方法
  • 第三步消息轉發的第二步能夠在+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector實現攔截類和實例方法並返回函數簽名
  • 第三步消息轉發的第三步能夠+ (void)forwardInvocation:(NSInvocation *)anInvocation- (void)forwardInvocation:(NSInvocation *)anInvocation實現類方法和實例方法的調用和獲取返回值

資料下載

本文章之因此圖片比較少,我以爲仍是跟着代碼敲一遍,印象比較深入。


最怕一輩子碌碌無爲,還安慰本身平凡難得。

廣告時間

相關文章
相關標籤/搜索