runtime分析記錄

runtime

isa

# runtime
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
    };
#endif
};
複製代碼
  • nonpointer: 表示是否對 isa 指針開啓指針優化 。 0,表明普通的指針,存儲着
  • Class、Meta-Class對象的內存地址 1,表明優化過,使用位域存儲更多的信息
  • has_assoc: 是否有設置過關聯對象,0沒有,1存在,若是沒有,釋放時會更快,
  • has_cxx_dtor: 是否有C++的析構函數(.cxx_destruct),若是有析構函數,則須要作析構邏輯, 若是沒有,則能夠更快的釋放對象。
  • shiftcls: 存儲着Class、Meta-Class對象的內存地址信息,在 arm64 架構中有 33 位用來存儲類指針。因此類對象、元類對象的地址最後3位都是0
  • magic: 用於在調試時分辨對象是否未完成初始化
  • weakly_referenced: 是否有被弱引用指向過,若是沒有,釋放時會更快
  • deallocating: 對象是否正在釋放
  • has_sidetable_rc: 對象引用計數是否大於 10,大於10 時,則須要借用sideTable進行存儲
  • extra_rc: 裏面存儲的值是引用計數器減1,表示該對象的引用計數值,其實是引用計數值減 1, 例如,若是對象的引用計數爲 10,那麼 extra_rc 爲 9。若是引用計數大於 10, 則須要使用到下面的 has_sidetable_rc。

class結構:

class_rw_t裏面的methods、properties、protocols是二維數組,是可讀可寫的,包含了類的初始內容、分類的內容數組

cache_t 方法緩存

sstruct cache_t {
    explicit_atomic<struct bucket_t *> _buckets; //散列表
    explicit_atomic<mask_t> _mask; //散列表長度-1

#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied; //已經緩存的方法數量

public:
    static bucket_t *emptyBuckets();
    
    struct bucket_t *buckets();
    mask_t mask();
    mask_t occupied();
    void incrementOccupied();
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
    void initializeToEmpty();

    unsigned capacity();
    bool isConstantEmptyCache();
    bool canBeFreed();

#if __LP64__
    bool getBit(uint16_t flags) const ;
    void setBit(uint16_t set) ;
    void clearBit(uint16_t clear) ;
#endif

#if FAST_CACHE_ALLOC_MASK
    bool hasFastInstanceSize(size_t extra) const;

    size_t fastInstanceSize(size_t extra) const;

    void setFastInstanceSize(size_t newSize);
#else
    bool hasFastInstanceSize(size_t extra) const {
        return false;
    }
    size_t fastInstanceSize(size_t extra) const {
        abort();
    }
    void setFastInstanceSize(size_t extra) {
        // nothing
    }
#endif

    static size_t bytesForCapacity(uint32_t cap);
    static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);

    void reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld);
    void insert(Class cls, SEL sel, IMP imp, id receiver);

    static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn, cold));
};


複製代碼
struct bucket_t {
private:
    explicit_atomic<uintptr_t> _imp;
    explicit_atomic<SEL> _sel;

    uintptr_t modifierForSEL(SEL newSel, Class cls) const {
        return (uintptr_t)&_imp ^ (uintptr_t)newSel ^ (uintptr_t)cls;
    }
modifiers.
    uintptr_t encodeImp(IMP newImp, SEL newSel, Class cls) const {
        if (!newImp) return 0;
		return (uintptr_t)newImp ^ (uintptr_t)cls;
    }

public:
    inline SEL sel() const { return _sel.load(memory_order::memory_order_relaxed); }

    inline IMP imp(Class cls) const {
        uintptr_t imp = _imp.load(memory_order::memory_order_relaxed);
        if (!imp) return nil;
		return (IMP)(imp ^ (uintptr_t)cls);
    }

    template <Atomicity, IMPEncoding>
    void set(SEL newSel, IMP newImp, Class cls);
};
複製代碼
  • 存入
void cache_t::insert(Class cls, SEL sel, IMP imp, id receiver)
{
    runtimeLock.assertLocked();

    ASSERT(sel != 0 && cls->isInitialized());

        if (slowpath(isConstantEmptyCache())) {
        // Cache is read-only. Replace it.
        if (!capacity) capacity = INIT_CACHE_SIZE;
        reallocate(oldCapacity, capacity, /* freeOld */false);
    }
    else if (fastpath(newOccupied <= capacity / 4 * 3)) {
        // Cache is less than 3/4 full. Use it as-is.
    }
    else {
        capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
        if (capacity > MAX_CACHE_SIZE) {
            capacity = MAX_CACHE_SIZE;
        }
        //空間不夠則空間*2開闢
        reallocate(oldCapacity, capacity, true);
    }

    bucket_t *b = buckets();
    mask_t m = capacity - 1;
    mask_t begin = cache_hash(sel, m);
    mask_t i = begin;

    do {
        if (fastpath(b[i].sel() == 0)) {
            //找到空的插入,而後cache_t的_occupied++
            incrementOccupied();
            b[i].set<Atomic, Encoded>(sel, imp, cls);
            return;
        }
        if (b[i].sel() == sel) {
            //已經存在直接返回
            return;
        }
    } while (fastpath((i = cache_next(i, m)) != begin));
    //沒有位置報錯
    cache_t::bad_cache(receiver, (SEL)sel, cls);
}

複製代碼

存入的位置是用bucket_t_imp&mask,mask=散列表長度-1,源碼以下緩存

static inline mask_t cache_hash(SEL sel, mask_t mask) 
{
    return (mask_t)(uintptr_t)sel & mask;
}
複製代碼

若是算出的位置是被佔用了,則會順序-1一直到空的地方,最多循環一圈,源碼以下:markdown

static inline mask_t cache_next(mask_t i, mask_t mask) {
    return i ? i-1 : mask;
}
複製代碼

method_t

typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 

struct method_t {
    SEL name; //名稱
    const char *types; //編碼值(返回值、參數)
    MethodListIMP imp; //函數地址
};
複製代碼
  1. SEL表明方法\函數名,通常叫作選擇器,底層結構跟char *相似
  2. 能夠經過@selector()和sel_registerName()得到
  3. 能夠經過sel_getName()和NSStringFromSelector()轉成字符串
  4. 不一樣類中相同名字的方法,所對應的方法選擇器是相同的
  5. types包含了函數返回值、參數編碼的字符串

iOS中提供了一個叫作@encode的指令,能夠將具體的類型表示成字符串編碼 架構


objc_msgSend

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

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

源碼分析

  • 消息發送

該階段會先去isa指向的類的cache中尋找方法,若是沒有找到則會去該類的rw中的methods中尋找,若該類沒有則像該類的superclass中尋找,尋找步驟依舊如上述所說,最後若是沒有找到則會來到動態解析(resove)。ide

ENTRY _objc_msgSend
	
	cbz	r0, LNilReceiver_f //若ro(self)爲0則跳轉LNilReceiver

	ldr	r9, [r0]		// r9 = self->isa
	GetClassFromIsa			// r9 = class
	CacheLookup NORMAL, _objc_msgSend //緩存尋找方法
	// cache hit, IMP in r12, eq already set for nonstret forwarding
	bx	r12			// call imp //直接跳轉r12地址 即imp

	CacheLookup2 NORMAL, _objc_msgSend 
	// cache miss
	ldr	r9, [r0]		// r9 = self->isa
	GetClassFromIsa			// r9 = class
	b	__objc_msgSend_uncached

LNilReceiver:
	// r0 is already zero
	mov	r1, #0
	mov	r2, #0
	mov	r3, #0
	FP_RETURN_ZERO
	bx	lr	        //等價於 mov pc,lr,即跳轉返回

END_ENTRY _objc_msgSend
複製代碼

cache中找不到則來到 __objc_msgSend_uncached函數

STATIC_ENTRY __objc_msgSend_uncached

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

	MethodTableLookup NORMAL	// returns IMP in r12 搜索方法列表
	bx	r12

END_ENTRY __objc_msgSend_uncached
複製代碼

該方法調用MethodTableLookup去搜索源碼分析

.macro MethodTableLookup
	
	stmfd	sp!, {r0-r3,r7,lr}
	add	r7, sp, #16
	sub	sp, #8			// align stack
	FP_SAVE

	// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
.if $0 == NORMAL
	// receiver already in r0
	// selector already in r1
.else
	mov 	r0, r1			// receiver
	mov 	r1, r2			// selector
.endif
	mov	r2, r9			// class to search
	mov	r3, #3			// LOOKUP_INITIALIZE | LOOKUP_INITIALIZE
	blx	_lookUpImpOrForward 
	mov	r12, r0			// r12 = IMP
	
.if $0 == NORMAL
	cmp	r12, r12		// set eq for nonstret forwarding
.else
	tst	r12, r12		// set ne for stret forwarding
.endif

	FP_RESTORE
	add	sp, #8			// align stack
	ldmfd	sp!, {r0-r3,r7,lr}

.endmacro
複製代碼
  • 動態解析

該方法其實會去調用_lookUpImpOrForward去尋找imp,若是imp不存在則會返回forward的imp優化

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior/*內含標記位,根據標記位去尋找方法*/)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache; //消息轉發
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (fastpath(behavior & LOOKUP_CACHE)) { 
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }


    runtimeLock.lock();

    
    checkIsKnownClass(cls);
    //初始化cls,包含rw等內容
    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }

    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
       
    }

    runtimeLock.assertLocked();
    curClass = cls;

    
    //循環遍歷cls以及父類方法列表
    for (unsigned attempts = unreasonableClassCount();;) { 
        // curClass method list.
        Method meth = getMethodNoSuper_nolock(curClass, sel); //遍歷class的rw的methods
        if (meth) {
            imp = meth->imp;
            goto done;
        }
        //子類沒有則獲取父類繼續尋找
        if (slowpath((curClass = curClass->superclass) == nil)) { 
            //一直沒有找到實現則賦值forward_imp
            imp = forward_imp;
            break;
        }

    
        // huan cun
        imp = cache_getImp(curClass, sel);
        if (slowpath(imp == forward_imp)) {
            // Found a forward:: entry in a superclass.
            // Stop searching, but don't cache yet; call method
            // resolver for this class first.
            break;
        }
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    // 沒有發現實現,則試着去調用resolver方法,而且只會調用一次
    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); //找到則緩存到clas本身的緩存中,不管方法是本類或父類的
    runtimeLock.unlock();
 done_nolock:
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    return imp;
}
複製代碼

lookUpImpOrForward中會嘗試調用cache_getImp再去找一下imp,找到則會跳轉到done_nolock返回,若是類沒有初始化則會去初始化類,包含rw等內容,下面就會循環遍歷cls以及父類方法列表,若是找到則跳轉done而後將方法緩存到該類的cache中,若是遍歷完了都沒有找到,則imp則會被賦值fordward,若是尚未嘗試過調用resolver方法,而且只會調用一次,保證調用一次的緣由是採用behavior的位值,關鍵代碼behavior ^= LOOKUP_RESOLVER,下面就是調用resolve的內容(元類和類的2種方法)ui

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    SEL resolve_sel = @selector(resolveInstanceMethod:);

    if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
        // 沒有實現Resolver則直接返回
        return;
    }

    //調用Resolver方法,在方法中咱們能夠動態添加方法實現
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel); //這裏能夠看出Resolver返回yes或者no都沒有影響,結果只是用來打印了

    //再次尋找sel實現,若是Resolver方法中,咱們已經動態添加了,則會把實現緩存下來
    IMP imp = lookUpImpOrNil(inst, sel, cls);

    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));
        }
    }
}
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
    runtimeLock.assertUnlocked();
    ASSERT(cls->isRealized());
    ASSERT(cls->isMetaClass());

    if (!lookUpImpOrNil(inst, @selector(resolveClassMethod:), cls)) {
        // Resolver not implemented.
        return;
    }

    Class nonmeta;
    {
        mutex_locker_t lock(runtimeLock);
        nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
        // +initialize path should have realized nonmeta already
        if (!nonmeta->isRealized()) {
            _objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
                        nonmeta->nameForLogging(), nonmeta);
        }
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(nonmeta, @selector(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(inst, sel, cls);

    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));
        }
    }
}

複製代碼

沒有實現resolver方法則直接返回,調用resolver方法,在方法中咱們能夠動態添加方法實現,在代碼中能夠看出resolver方法返回yes或者no都沒有影響,調用完了以後,會再次尋找sel實現,若是rsolver方法中,咱們已經動態添加了,則會把實現緩存下來。

  • 消息轉發

最後若是都沒有發現,則會進入消息轉發,若是咱們沒有實現forward方法則會報錯方法找不到。 下方是流程圖

相關文章
相關標籤/搜索