2021- iOS 面試題分析(二)

1.回顧

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

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:翻到具體的內容

  • impSEL 的關係

SEL : ⽅法編號 IMP : 函數指針地址 SEL 相當於書本⽬錄的名稱 IMP : 相當於書本⽬錄的⻚碼

  1. ⾸先明⽩我們要找到書本的什麼內容 (sel ⽬錄⾥⾯的名稱)
  2. 通過名稱找到對應的本⻚碼 (imp)
  3. 通過⻚碼去定位具體的內容

2.2 能否向編譯後的得到的類中增加實例變量?能否向運⾏時創建的類中添加實例變量

不能向編譯後的得到的類中增加實例變量

原因:我們編譯好的實例變量存儲的位置在 ro,⼀旦編譯完成,內存結構就完全確定了,是⽆法進行任何修改的。

只要內沒有註冊到內存還是可以添加

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

主要用到了objc_setAssociatedObjectobjc_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]打印就很懵了

太意想不到了,那麼clang一下看看,是objc_msgSendSuper

之後debug一下,彙編跟蹤看看

不看不要緊,一看更懵逼!什麼鬼👻???不是發送objc_msgSendSuper消息嗎?怎麼又變成objc_msgSendSuper2了啊!現在腦殼嗡嗡的!百思不得其解。

- (Class)class {
    return object_getClass(self);
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}
  • classNSObject的方法,class的隱藏參數是id self, SEL _cmd。所以[self class] 就是發送消息objc_msgSend,消息接受者是 self 和方法編號class。所以返回LGTeacher
  • 對於super來說它是沒有這個參數的。它不是參數名,是一個編譯器關鍵字。在clang中編譯後調用的是objc_msgSendSuper方法。
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )

objc_msgSendSuper有兩個參數objc_superSEL

  • 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_msgSendSuperobjc_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_supersuperClass爲父類,objc_msgSendSuper2傳遞的objc_supersuperClass爲自己,在彙編代碼中進行了父類的獲取。

那麼[super class]receiver決定了消息的接收者,從上面的解釋中也可以知道,這裏的接受者還是self也就是LGTeacher

所以[super class]也打印的是LGTeacher

  • llvm中實現源碼如下:

更多內容持續更新

🌹 喜歡就點個贊吧👍🌹

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

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

如果你正在跳槽或者正準備跳槽不妨動動小手,添加一下咱們的交流羣1012951431來獲取一份詳細的大廠面試資料爲你的跳槽多添一份保障。

文末推薦:iOS熱門文集

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章