1.回顧
在之前的博客中,對OC底層進行了一系列的源碼的探索分析,上一篇博客也對一些面試題進行了回答和分析,本篇博客繼續面試題分析!
2. iOS面試題分析
2.1 ⽅法的本質?sel是什麼?IMP是什麼?兩者之間的關係⼜是什麼?
- 方法的本質:發送消息流程
- 快速消息查找 (
objc_msgSend
),cache_t
緩存查找消息。 - 慢速消息查找(
lookUpImpOrForward
)遞歸自己以及父類,自己找不到去父類緩存中找,依然找不到會進行父類慢速查找,直到找到nil。 - 查找不到消息進行動態方法解析(
resolveInstanceMethod
/resolveClassMethod
)。resolveClassMethod
的過程中如果沒有找到方法,會調用resolveInstanceMethod
。 - 消息快速轉發(
forwardingTargetForSelector
),相當於找消息備用接收者。 - 消息慢速轉發(
methodSignatureForSelector
&forwardInvocation
),在仍然沒有解決問題後在methodSignatureForSelector
的時候會再進行一次慢速消息查找(這次不進行消息轉發)。 - 最後仍然沒有解決問題會進入
doesNotRecognizeSelector
拋出異常。
-
sel
是方法編號,在read_images
期間就編譯進入了內存。 -
imp
就是函數實現指針 ,找imp
就是找函數的過程。
可以將sel-imp
理解爲書本的目錄,sel
書本目錄的名稱,imp
就是書本的⻚碼。查找具體的函數就是想看這本書裏面具體篇章的內容。
1:我們⾸先知道想看什麼 -- >
tittle (sel)
2:根據⽬錄對應的⻚碼 -- >
(imp)
3:翻到具體的內容
imp
與SEL
的關係
SEL
: ⽅法編號 IMP
: 函數指針地址 SEL
相當於書本⽬錄的名稱 IMP
: 相當於書本⽬錄的⻚碼
- ⾸先明⽩我們要找到書本的什麼內容 (
sel
⽬錄⾥⾯的名稱) - 通過名稱找到對應的本⻚碼 (
imp
) - 通過⻚碼去定位具體的內容
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]
打印就很懵了
太意想不到了,那麼
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;
}
-
class
是NSObject
的方法,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_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
中實現源碼如下:
更多內容持續更新
🌹 喜歡就點個贊吧👍🌹
🌹 覺得有收穫的,可以來一波,收藏+關注,評論 + 轉發,以免你下次找不到我😁🌹
🌹歡迎大家留言交流,批評指正,互相學習😁,提升自我🌹
如果你正在跳槽或者正準備跳槽不妨動動小手,添加一下咱們的交流羣1012951431來獲取一份詳細的大廠面試資料爲你的跳槽多添一份保障。