天天看點

iOS 面試題分析(二)

1.回顧

在之前的部落格中,對OC底層進行了一系列的源碼的探索分析,上一篇部落格也對一些面試題進行了回答和分析,本篇部落格繼續面試題分析!

iOS 面試題分析(二)

2. iOS面試題分析

2.1 ⽅法的本質?sel是什麼?IMP是什麼?兩者之間的關系⼜是什麼?

  • 方法的本質:發送消息流程
  1. 快速消息查找 (

    objc_msgSend

    ),

    cache_t

    緩存查找消息。
  2. 慢速消息查找(

    lookUpImpOrForward

    )遞歸自己以及父類,自己找不到去父類緩存中找,依然找不到會進行父類慢速查找,直到找到nil。
  3. 查找不到消息進行動态方法解析(

    resolveInstanceMethod

    /

    resolveClassMethod

    )。

    resolveClassMethod

    的過程中如果沒有找到方法,會調用

    resolveInstanceMethod

  4. 消息快速轉發(

    forwardingTargetForSelector

    ),相當于找消息備用接收者。
  5. 消息慢速轉發(

    methodSignatureForSelector

    &

    forwardInvocation

    ),在仍然沒有解決問題後在

    methodSignatureForSelector

    的時候會再進行一次慢速消息查找(這次不進行消息轉發)。
  6. 最後仍然沒有解決問題會進入

    doesNotRecognizeSelector

    抛出異常。
  • sel

    是方法編号,在

    read_images

    期間就編譯進入了記憶體。
  • imp

    就是函數實作指針 ,找

    imp

    就是找函數的過程。

    可以将

    sel-imp

    了解為書本的目錄,

    sel

    書本目錄的名稱,

    imp

    就是書本的⻚碼。查找具體的函數就是想看這本書裡面具體篇章的内容。
  • 1:我們⾸先知道想看什麼 – >

    tittle (sel)

  • 2:根據⽬錄對應的⻚碼 – >

    (imp)

  • 3:翻到具體的内容
  • imp

    SEL

    的關系

    SEL

    : ⽅法編号

    IMP

    : 函數指針位址

    SEL

    相當于書本⽬錄的名稱

    IMP

    : 相當于書本⽬錄的⻚碼
  1. ⾸先明⽩我們要找到書本的什麼内容 (

    sel

    ⽬錄⾥⾯的名稱)
  2. 通過名稱找到對應的本⻚碼 (

    imp

    )
  3. 通過⻚碼去定位具體的内容

2.2 能否向編譯後的得到的類中增加執行個體變量?能否向運⾏時建立的類中添加執行個體變量

不能向編譯後的得到的類中增加執行個體變量

原因

:我們編譯好的執行個體變量存儲的位置在

ro

,⼀旦編譯完成,記憶體結構就完全确定了,是⽆法進行任何修改的。
隻要内沒有注冊到記憶體還是可以添加

可以通過關聯對象的方式添加屬性,方法等

主要用到了

objc_setAssociatedObject

objc_getAssociatedObject

以及

objc_removeAssociatedObjects

方法

當我們的對象釋放的時候 -->

dealloc

1:

c++

函數釋放:

object_cxxDestruct

2: 移除關聯屬性 :

_object_remove_assocations

3: 将弱引⽤⾃動設定

nil

:

weak_clear_no_lock(&table.weak_table, (id)this)

4: 引⽤計數處理:

table.refcnts.erase(this)

4: 銷毀對象:

free(obj)

2.3 [self class]和[super class]的差別以及原理分析。

我先來看看如下,代碼

@implementation LGTeacher
- (instancetype)init{
    self = [super init];
    if (self) {
        NSLog(@"%@ - %@",[self class],[super class]);
    }
    return self;
}
           

代碼運作結果如下:

2021-07-30 10:49:10.213169+0800 ObjcBuild[65615:2453340] LGTeacher--LGTeacher
           

[self class]

列印結果可以了解,這

[super class]

列印就很懵了
iOS 面試題分析(二)
太意想不到了,那麼

clang

一下看看,是

objc_msgSendSuper

iOS 面試題分析(二)
之後

debug

一下,彙編跟蹤看看
iOS 面試題分析(二)
不看不要緊,一看更懵逼!什麼鬼👻???不是發送

objc_msgSendSuper

消息嗎?怎麼又變成

objc_msgSendSuper2

了啊!現在腦殼嗡嗡的!百思不得其解。
- (Class)class {
    return object_getClass(self);
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}
           
  • class

    NSObject

    的方法,

    class

    的隐藏參數是

    id self

    ,

    SEL _cmd

    。是以

    [self class]

    就是發送消息

    objc_msgSend

    ,消息接受者是

    self

    和方法編号

    class

    。是以傳回

    LGTeacher

  • 對于

    super

    來說它是沒有這個參數的。它不是參數名,是一個

    編譯器關鍵字

    。在

    clang

    中編譯後調用的是

    objc_msgSendSuper

    方法。

objc_msgSendSuper

有兩個參數

objc_super

SEL

  • objc_super

/// Specifies the superclass of an instance. 
struct objc_super {
    __unsafe_unretained _Nonnull id receiver;
#if !defined(__cplusplus)  &&  !__OBJC2__
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
};
#endif
           

其中的一個參數

receiver

是消息接收者,

super_class

為第一個被查找的類,但是實際它調用的是

objc_msgSendSuper2

,上面也驗證過了。

  • objc_msgSendSuper2

// objc_msgSendSuper2() takes the current search class, not its superclass.
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
           

從源碼的注釋中也可以發現

super_class

應該就是目前類。

  • objc_msgSendSuper

    objc_msgSendSuper2

    實作如下:
ENTRY _objc_msgSendSuper
    UNWIND _objc_msgSendSuper, NoFrame
    //p0存儲receiver,p16存儲class
    ldp p0, p16, [x0]       // p0 = real receiver, p16 = class
    //跳轉到 L_objc_msgSendSuper2_body 的實作
    b L_objc_msgSendSuper2_body

    END_ENTRY _objc_msgSendSuper

    // no _objc_msgLookupSuper

    ENTRY _objc_msgSendSuper2
    UNWIND _objc_msgSendSuper2, NoFrame

#if __has_feature(ptrauth_calls)
    ldp x0, x17, [x0]       // x0 = real receiver, x17 = class
    //讀取
    add x17, x17, #SUPERCLASS   // x17 = &class->superclass
    ldr x16, [x17]      // x16 = class->superclass
    AuthISASuper x16, x17, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS
LMsgSendSuperResume:
#else
    //ldp讀取兩個寄存器,将objc_super解析成receiver和class
    ldp p0, p16, [x0]       // p0 = real receiver, p16 = class
    //通過class找到superclass
    ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
#endif
L_objc_msgSendSuper2_body:
    //查找緩存
    CacheLookup NORMAL, _objc_msgSendSuper2, __objc_msgSend_uncached

    END_ENTRY _objc_msgSendSuper2
           

從上面

arm64

的彙編源碼可以知道

_objc_msgSendSuper

跳轉到

_objc_msgSendSuper2

,差別是

_objc_msgSendSuper

直接調用,

objc_msgSendSuper2

通過

cls

擷取了

superClass

。也就是說

objc_msgSendSuper

傳遞的

objc_super

superClass

為父類,

objc_msgSendSuper2

傳遞的

objc_super

superClass

為自己,在彙編代碼中進行了父類的擷取。

那麼

[super class]

receiver

決定了消息的接收者,從上面的解釋中也可以知道,這裡的接受者還是

self

也就是

LGTeacher

是以

[super class]

也列印的是

LGTeacher

  • llvm

    中實作源碼如下:
iOS 面試題分析(二)

更多内容持續更新

🌹 喜歡就點個贊吧👍🌹

🌹 覺得有收獲的,可以來一波,收藏+關注,評論 + 轉發,以免你下次找不到我😁🌹

🌹歡迎大家留言交流,批評指正,互相學習😁,提升自我🌹