iOS runtime 部分三

主要探究 objc_msgSend()的流程;

文中使用的 objc4源碼是objc-781版本;

runtime 部分一
runtime 部分二
runtime 部分三


1. objc_msgSend()的流程;

首先我們都知道, 我們調(diào)用方法后到底層就是通過objc_msgSend()來實現(xiàn)的, 這個機(jī)制就是我們所說的消息發(fā)送機(jī)制, 這個過程分為消息發(fā)送 動態(tài)解析 消息轉(zhuǎn)發(fā)三個階段;
轉(zhuǎn)化為objc_msgSend()有兩個參數(shù), 第一個是接收者receiver, 第二個是SEL;

///不論是類方法還是實例方法底層都是objc_msgSend()方式實現(xiàn);
Person *person = [[Person alloc] init];
[person realizedMehod];
///轉(zhuǎn)化為C++后代碼如下
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("realizedMehod"));

由于 objc_msgSend()的實現(xiàn)過程是通過匯編實現(xiàn), 本人能力有限, 只能大致推測下, 從 objc_msgSend()開始的整個流程, 如果有;理解錯誤的地方還請不吝賜教;
注意匯編中調(diào)用 C 或者 C++的函數(shù)會在函數(shù)名之前加上_, 例如objc_msgSend()在匯編中對應(yīng)的就是_objc_msgSend;

///入口
/********************************************************************
 *
 * id objc_msgSend(id self, SEL _cmd, ...);
 * IMP objc_msgLookup(id self, SEL _cmd, ...);
 * 
 * objc_msgLookup ABI:
 * IMP returned in x17
 * x16 reserved for our use but not used
 *
 ********************************************************************/
....
        //_objc_msgSend開始
    ENTRY _objc_msgSend
    UNWIND _objc_msgSend, NoFrame
    cmp p0, #0          // nil check and tagged pointer check
///在 arm64架構(gòu)下SUPPORT_TAGGED_POINTERS = 1 , 所以下一步LNilOrTagged, 檢查 receiver 是否為空;
#if SUPPORT_TAGGED_POINTERS
    b.le    LNilOrTagged        //  (MSB tagged pointer looks negative)
#else
    b.eq    LReturnZero
#endif
    ldr p13, [x0]       // p13 = isa
        ///通過 isa 獲取 class
    GetClassFromIsa_p16 p13     // p16 = class
LGetIsaDone:
    // calls imp or objc_msgSend_uncached
        ///開始緩存查找, 注意入?yún)⑹荖ORMAL
    CacheLookup NORMAL, _objc_msgSend

#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
    b.eq    LReturnZero     // nil check

    // 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
END_ENTRY _objc_msgSend

===>
///緩存查找
.macro CacheLookup
LLookupStart$1:
    // p1 = SEL, p16 = isa
    ldr p11, [x16, #CACHE]              // p11 = mask|buckets
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
        /*
          arm64架構(gòu)執(zhí)行這里的邏輯;
          雖然我們不知道匯編的具體作用, 但是從注釋依然可以看出一些信息. 從散列表(buckets)中
          通過_cmd & mask來獲取方法 IMP, 這個跟之前的方法緩存的算法和過程相對應(yīng);
        */
    and p10, p11, #0x0000ffffffffffff   // p10 = buckets
    and p12, p1, p11, LSR #48       // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    and p10, p11, #~0xf         // p10 = buckets
    and p11, p11, #0xf          // p11 = maskShift
    mov p12, #0xffff
    lsr p11, p12, p11               // p11 = mask = 0xffff >> p11
    and p12, p1, p11                // x12 = _cmd & mask
#else

...

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    add p12, p12, p11, LSL #(1+PTRSHIFT)
                    // p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif

    // 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
      ///找到緩存 call or return imp
    CacheHit $0         // call or return imp
    
2:  // not hit: p12 = not-hit bucket
      ///緩存沒有找到,  調(diào)用CheckMiss
    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

LLookupEnd$1:
LLookupRecover$1:
3:  // double wrap
    JumpMiss $0

.endmacro

===>
///宏定義CheckMiss
.macro CheckMiss
    // miss if bucket->sel == 0
.if $0 == GETIMP
    cbz p9, LGetImpMiss
///之前的入?yún)⑹荖ORMAL, 下一步調(diào)用__objc_msgSend_uncached
.elseif $0 == NORMAL
    cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
    cbz p9, __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 p16 is the class to search
///調(diào)用MethodTableLookup
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached

===>

.macro MethodTableLookup
...
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
///調(diào)用lookUpImpOrForward方法
bl  _lookUpImpOrForward
...

下面就是到源碼lookUpImpOrForward的實現(xiàn);
===>

//**********************************************************************************************//
在arm64-asm.h文件中可以得知在 arm64 & __LP64__模式下SUPPORT_TAGGED_POINTERS = 1
#if __arm64__
#if __LP64__
// true arm64
#define SUPPORT_TAGGED_POINTERS 1
....
===
在objc-config.h文件中我們可以得知在 arm64 &  __LP64__模式下 CACHE_MASK_STORAGE = CACHE_MASK_STORAGE_HIGH_1
#define CACHE_MASK_STORAGE_OUTLINED 1
#define CACHE_MASK_STORAGE_HIGH_16 2
#define CACHE_MASK_STORAGE_LOW_4 3
#if defined(__arm64__) && __LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#elif defined(__arm64__) && !__LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
#endif
//**********************************************************************************************//

上面的過程主要是記錄下在匯編層面objc_msgSend()的流程, 下面著重說下lookUpImpOrForward的實現(xiàn);

/***********************************************************************
* lookUpImpOrForward.
* The standard IMP lookup. 
標(biāo)準(zhǔn)的 IMP 查找方法
...
*   If you don't want forwarding at all, use LOOKUP_NIL.
注意這句話, 如果你完全不想使用消息轉(zhuǎn)發(fā), 則使用LOOKUP_NIL
**********************************************************************/
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    ///獲取消息轉(zhuǎn)發(fā)的 IMP
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
     ///初始的imp為空
    IMP imp = nil;
    ///當(dāng)前類
    Class curClass;
    ///runtime鎖
    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
      ///查找方法緩存, 如果查找到緩存直接結(jié)束流程將方法返回
    if (fastpath(behavior & LOOKUP_CACHE)) {
        imp = cache_getImp(cls, sel);
        if (imp) goto done_nolock;
    }  
    ///檢查類的initialize相關(guān)
     runtimeLock.lock();
     checkIsKnownClass(cls);
    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }
    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }
    runtimeLock.assertLocked();
    curClass = cls;

    ///核心方法, 通過向上遍歷當(dāng)前類的父類, 來查找imp
    for (unsigned attempts = unreasonableClassCount();;) {
        // curClass method list.
        ///獲取當(dāng)前類的方法列表, 不查詢父類
        Method meth = getMethodNoSuper_nolock(curClass, sel);
        ///如果當(dāng)前類中查找到imp則結(jié)束查找 去done:出緩存imp然后返回這個imp;
        if (meth) {
            imp = meth->imp;
            goto done;
        }
         /*
          注意這個地方的寫法, 執(zhí)行完這句代碼后, curClass就已經(jīng)指向父類了;
          if (slowpath((curClass = curClass->superclass) == nil)) {}
          等同于下面兩句語句組合, 如果有疑問可以自行測試下;
          curClass = curClass->superclass
          if (slowpath(curClass  == nil) {}
        
        如果查詢到基類仍然沒有查找大相關(guān)方法, 則使用消息轉(zhuǎn)發(fā)(注意這里只是跳出循環(huán))
        實際上下面要先判斷動態(tài)解析, 動態(tài)解析是先于消息轉(zhuǎn)發(fā)的;
         */
        if (slowpath((curClass = curClass->superclass) == nil)) {
            // No implementation found, and method resolver didn't help.
            // Use forwarding.
            imp = forward_imp;
            break;
        }
    
        // Halt if there is a cycle in the superclass chain.
        if (slowpath(--attempts == 0)) {
            _objc_fatal("Memory corruption in class list.");
        }

        // Superclass cache.
        /*
        從curClass已經(jīng)指向了父類, 所以這里判斷父類中是否有消息轉(zhuǎn)發(fā), 
        如果子類沒有消息轉(zhuǎn)發(fā)相關(guān)處理, 寫在父類中實現(xiàn)消息轉(zhuǎn)發(fā)也會有效;
        仍然是跳出循環(huán), 先去動態(tài)解析;
        */
        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;
        }  
        //如果父類中方法緩存中查找到了, 則將方法緩存到本類然后返回imp
        if (fastpath(imp)) {
            // Found the method in a superclass. Cache it in this class.
            goto done;
        }
    }

    // No implementation found. Try method resolver once.
    /*  
      如果沒有找到IMP則開始動態(tài)解析, 不論是否成功最后都會再次調(diào)用lookUpImpOrForward;
      注意只進(jìn)行一次動態(tài)解析, 如果動態(tài)解析成功, 則將相關(guān)方法緩存到本類;
      下次再次調(diào)用時則是直接查找類中方法列表即可;
      如果動態(tài)解析失敗, 則再次lookUpImpOrForward, 重新開始流程, 進(jìn)入消息轉(zhuǎn)發(fā)階段;
      具體請看resolveMethod_locked的官方注釋和實現(xiàn)流程;
     */
    if (slowpath(behavior & LOOKUP_RESOLVER)) {
        behavior ^= LOOKUP_RESOLVER;
        return resolveMethod_locked(inst, sel, cls, behavior);
    }

 done:
    ///將查找到的imp緩存
    log_and_fill_cache(cls, imp, sel, inst, curClass);
    runtimeLock.unlock();
 done_nolock:
    ///如果完全不想使用消息轉(zhuǎn)發(fā), 但是獲取到的緩存imp=forward_imp則直接返回nil
    if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
        return nil;
    }
    ///最終返回imp
    return imp;
}


//******************************************************************//
///動態(tài)解析方法入口
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior) {
    runtimeLock.assertLocked();
    ASSERT(cls->isRealized());
    runtimeLock.unlock();
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        resolveInstanceMethod(inst, sel, cls);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        resolveClassMethod(inst, sel, cls);
        if (!lookUpImpOrNil(inst, sel, cls)) {
            resolveInstanceMethod(inst, sel, cls);
        }
    }
    // chances are that calling the resolver have populated the cache
    // so attempt using it
    return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
===>
///以實例方法為例, 動態(tài)解析方法入口中會調(diào)用  resolveInstanceMethod方法

/***********************************************************************
* resolveInstanceMethod
* Call +resolveInstanceMethod, looking for a method to be added to class cls.
   ///尋找要添加到類中的method;
* cls may be a metaclass or a non-meta class.
* Does not check if the method already exists.
**********************************************************************/
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 not implemented.
        return;
    }
    BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
    bool resolved = msg(cls, resolve_sel, sel);
    // Cache the result (good or bad) so the resolver doesn't fire next time.
    //不論好壞將結(jié)果緩存起來, 動態(tài)解析不會再觸發(fā)
    // +resolveInstanceMethod adds to self a.k.a. cls
    IMP imp = lookUpImpOrNil(inst, sel, cls);
 ...
}

至此objc_msgSend()的流程大致結(jié)束, 下面代碼測試下動態(tài)解析和消息轉(zhuǎn)發(fā);

2. 動態(tài)解析

兩個關(guān)鍵方法: 實例方法+(BOOL)resolveInstanceMethod: ; 類方法+(BOOL)resolveClassMethod:
以實例方法為例, 簡單的理解為:
調(diào)用notRealizeMethod方法后后, 如果本類或者父類實現(xiàn)了這個方法則調(diào)用, 否則檢查是否實現(xiàn)動態(tài)解析方法,
實現(xiàn)了resolveInstanceMethod方法,開始動態(tài)解析; 這個地方就是動態(tài)為本類添加一個方法去映射實現(xiàn)notRealizeMethod;
沒有實現(xiàn)resolveInstanceMethod, 程序crash拋出unrecognized selector sent to instance;
開始動態(tài)解析;

調(diào)用未實現(xiàn)的方法notRealizeMethod
Cat *cat = [[Cat alloc] init];
[cat notRealizeMethod];
///Cat的實現(xiàn)如下;
//.h文件
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Cat : NSObject
///此方法未實現(xiàn)
- (void)notRealizeMethod;
@end
NS_ASSUME_NONNULL_END
///.m文件為
#import "Cat.h"
#import <objc/runtime.h>
@implementation Cat
- (void)HandleNotRealizedMethod {
    NSLog(@"HandleNotRealizedMethod : %s", __func__);
}
/*
 對notRealizeMethod方法, 沒有實現(xiàn), 調(diào)用后;
 實現(xiàn)了resolveInstanceMethod方法,開始動態(tài)解析;
 沒有實現(xiàn)resolveInstanceMethod, 程序crash拋出unrecognized selector sent to instance;
 開始動態(tài)解析
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel  == @selector(notRealizeMethod)) {
        SEL handelSel = @selector(HandleNotRealizedMethod);
        Method handleMethod = class_getInstanceMethod(self, handelSel);
        IMP  imp  = class_getMethodImplementation(self, handelSel);
        /*
         為某個類添加Method;
         參數(shù)1: 為哪個類添加方法;
         參數(shù)2: 為哪個方法添加實現(xiàn);
         參數(shù)3: 方法的具體實現(xiàn);
         參數(shù)4: 方法的編碼格式;
         */
        class_addMethod(self, sel, imp, method_getTypeEncoding(handleMethod));
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
@end
///運(yùn)行后的結(jié)果為
2020-07-04 17:36:59.568045+0800 objc_msgSend[11787:194063] HandleNotRealizedMethod : -[Cat HandleNotRealizedMethod]

3. 消息轉(zhuǎn)發(fā)

如果動態(tài)解析方法沒有實現(xiàn), 或者沒有處理動態(tài)解析, 則進(jìn)入消息轉(zhuǎn)發(fā)階段;
以實例方法為例, 大致流程為:
幾個主要的方法;
- (id)forwardingTargetForSelector:()返回一個能處理notRealizeInstanceMethod的對象;
- (NSMethodSignature *)methodSignatureForSelector:()方法簽名;
- (void)forwardInvocation:()最終的處理

注意: 類方法也有消息轉(zhuǎn)發(fā);
把相關(guān)的方法打出后手動改為+即可;處理的流程跟實例方法類似;
+ (id)forwardingTargetForSelector:();
+ (NSMethodSignature *)methodSignatureForSelector:();
+ (void)forwardInvocation:();

測試代碼

///調(diào)用方法
Pig *pig = [[Pig alloc] init];
[pig notRealizeInstanceMethod];
[Pig notRealizeClassMethod];
   

///動態(tài)解析階段, 不處理或者處理不成功, 進(jìn)入消息轉(zhuǎn)發(fā)階段
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return [super resolveInstanceMethod:sel];
}

/*
 aSelector這個時候就是notRealizeInstanceMethod;
 這個地方需要返回一個值, 就是返回一個能處理notRealizeInstanceMethod的對象;
 例如Piggy也有實例方法notRealizeInstanceMethod, 并且也實現(xiàn)了,這時可以返回Piggy的實例對象;即可處理方法;
 */
- (id)forwardingTargetForSelector:(SEL)aSelector {
//如過將下面注釋的代碼打開, 則直接將消息轉(zhuǎn)發(fā)給Piggy的實例對象, 方法簽名的相關(guān)方法不再生效;
    ///如果發(fā)現(xiàn)Pig對象調(diào)用notRealizeInstanceMethod方法, 方法沒有實現(xiàn), 并且動態(tài)解析失敗, 則將消息轉(zhuǎn)發(fā)給Piggy對象;  
//    if (aSelector == @selector(notRealizeInstanceMethod)) {
//        Piggy *piggy =  [[Piggy alloc] init];
//        /*
//         消息轉(zhuǎn)發(fā)的過程, 源碼不開源, 并不能找到相關(guān)流程, 但是消息轉(zhuǎn)發(fā)后有做一個操作就是objc_msgSend( piggy,  aSelector);
//         就是讓piggy調(diào)用aSelector;
//         */
//        return piggy;
//    }
    return [super forwardingTargetForSelector:aSelector];
}


/*
 如果forwardingTargetForSelector并不能返回一個有效的對象; 開始進(jìn)入方法簽名階段
 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    /*
     方法編碼, 要返回aSelector的編碼格式;
     如果返回一個合理的值, 則調(diào)用forwardInvocation方法;
     */
    NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    return sig;
}


/*
 到了這一步, 可以做任何操作了
 類似KVC是實現(xiàn)了setValue: forUndefinedKey:即使沒有相應(yīng)key, 實現(xiàn)了此方法, 什么都不做也不會崩潰;
 
 NSInvocation封裝了一個方法的調(diào)用信息; 調(diào)用者, 方法, 方法編碼;
 target: 方法之前的調(diào)用者, 可更改為其他調(diào)用者;
 selector : 需要調(diào)用的方法, 可更改為其他方法;
 methodSignature : 方法的簽名信息; 不可更改;

 只要target和selector是配套合理的,methodSignature可以忽略;例如:
 anInvocation.target = [Dog class];
 anInvocation.selector = @selector(classTest:);
 [anInvocation invoke];
 */
-  (void)forwardInvocation:(NSInvocation *)anInvocation {
    anInvocation.target = [Dog class];
    anInvocation.selector = @selector(classTest:);
    [anInvocation invoke];
 }

以類方法為例驗證另一個問題: 子類中調(diào)用沒有實現(xiàn)方法, 且沒有做方法的消息轉(zhuǎn)發(fā), 但是父類實現(xiàn)了消息轉(zhuǎn)發(fā), 也會有效;

//調(diào)用方法
[Pig notRealizeClassMethod];

///Pig的父類Animal的.m實現(xiàn)為
#import "Animal.h"
@implementation Animal
+  (id)forwardingTargetForSelector:(SEL)aSelector {
    return [super forwardingTargetForSelector:aSelector];
}
+  (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    return sig;
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"Pig類中沒有實現(xiàn)notRealizeClassMethod方法, 且沒有做類方法的消息轉(zhuǎn)發(fā), 但是父類實現(xiàn)了消息轉(zhuǎn)發(fā), 也會有效;");
}
@end

2020-07-04 18:16:20.022633+0800 objc_msgSend[15196:261753] Pig類中沒有實現(xiàn)notRealizeClassMethod方法, 
且沒有做類方法的消息轉(zhuǎn)發(fā), 但是父類實現(xiàn)了消息轉(zhuǎn)發(fā), 也會有效;

4. 流程圖總結(jié)消息發(fā)送, 動態(tài)解析, 消息轉(zhuǎn)發(fā)的過程

  • 消息發(fā)送階段的流程:


  • 動態(tài)解析階段流程:


    image.png
  • 消息轉(zhuǎn)發(fā)流程
    類方法的處理流程類似, 把相關(guān)的-號方法換成+號方法;


文中測試代碼


參考文章和下載鏈接
Apple 一些源碼的下載地址
方法的查找順序
什么是散列表
LP64 結(jié)構(gòu)數(shù)據(jù)占據(jù)多少位
LP64什么意思
匯編和 C 函數(shù)的相互調(diào)用
iOS 方法簽名機(jī)制
iOS方法返回值和參數(shù)對應(yīng)的Type Encodings

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容