1. 回顧
在之前的幾篇博客里面,已經對OC類的底層結構進行了分析,并對內部主要的成員變量(isa/bits)做了詳細的分析。在上兩個博客
iOS底層探索之類的結構—cache分析(上)
iOS底層探索之類的結構—cache分析(下)
對類中的cache
做了比較詳細的分析。后面通過斷點
查看匯編
可以發現在insert
方法調用流程之前,還有一個cache
讀取流程,objc_msgSend
和 cache_getImp
。這就涉及到Runtime
的知識點了,之前的內容都是承上啟下的,是互相關聯的。
2. Runtime
2.1 什么是Runtime
runtime
翻譯過來稱為運行時,與之對應的是編譯時
。大部分的iOS開發人員,都聽過runtime
這個詞,也知道運行時。但只是停留在表面,只是知道而已,并沒有去深入的去探索和分析過。
OC
語言是一門動態語言
,擁有動態語言的三大特性:動態類型
、動態綁定
、動態加載
。而底層實現就是熟悉又陌生的Runtime
。
-
運行時
是一種面向對象的編程語言(面向對象編程)的運行環境。運行時表明了在某個時間段內,哪個程序正在運行。運行時是計算機程序運行生命周期內的一個階段,其它階段還包括:編譯時、鏈接時和加載時。簡單理解就是, 代碼跑起來,被裝載到內存中的過程。(你的代碼保存在磁盤上沒裝入內存之前是個死家伙,只有跑到內存中才變成活的)。 -
編譯時
顧名思義就是正在編譯的時候。那什么叫編譯
呢?就是編譯器幫你把源代碼翻譯成機器能識別的二進制代碼 。 - (當然只是一般意義上這么說,實際上可能只是翻譯成某個中間狀態的語言。比如
Java
只有JVM
識別的字節碼,C#
中只有CLR
能識別的MSIL
。另外還有鏈接器、匯編器、為了了便于理解我們可以統稱為編譯器) - 那編譯時就是簡單的作一些翻譯工作,比如檢查老兄你有沒有粗心寫錯啥
關鍵字
了。 -
詞法分析
,語法分析之類的過程。就像個老師檢查學生的作文中有沒有錯別字和病句一樣 。 - 如果發現啥錯誤編譯器就告訴你,平時使用
Xcode
時,點下build
那就開始編譯。 - 如果下面有
errors
或者warning
信息,那都是編譯器檢查出來的。這時的錯誤就叫編譯時
錯誤,這個過程中做的類型檢查也就叫編譯時類型檢查
,或靜態類型檢查
(所謂靜態嘛就是沒把真把代碼放內存中運行起來,而只是把代碼當作文本來掃描下)。
2.2 runtime的使用的三種方式
runtime
的使用的三種方式,其三種實現方法與編譯層和底層的關系如圖所示
通過
OC
上層的代碼實現,例如[JPerson hello]
通過
NSObject
方法實現,例如isKindOfClass
-
通過
Runtime API
底層方法實現,例如class_getInstanceSize
Runtime三種方式及底層的關系圖
圖中的
compiler
就是編譯器,就是我們熟悉的LLVM
3. OC方法的本質
在之前的一篇博客iOS開發之結構體底層探索我們知道平時寫的OC
代碼,底層實現其實都是C/C++
的代碼實現的,再經過編譯器LLVM
編譯,最終轉化為機器語言。
通過clang
編譯的源碼,理解了OC
對象的本質(結構體
),同樣的,我們也可以使用clang
命令編譯成main.cpp
文件,看看方法的本質
是什么?
3.1 objc_msgSend
- 編譯前
@interface JPPerson : NSObject
@property (nonatomic, readwrite , copy) NSString *personName;
- (void)superTest;
@end
@implementation JPPerson
- (void)superTest {
NSLog(@"這是父類");
}
@end
@interface JPStudent : JPPerson
@property (nonatomic, readwrite , copy) NSString *studentName;
- (void)test;
@end
@implementation JPStudent
- (void)test {
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
JPStudent *stu = [[JPStudent alloc]init];
[stu test];
[stu superTest];
}
return 0;
}
- 編譯后
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
JPStudent *stu = ((JPStudent *(*)(id, SEL))(void *)objc_msgSend)((id)((JPStudent *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("JPStudent"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)stu, sel_registerName("test"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)stu, sel_registerName("superTest"));
}
return 0;
}
通過上述代碼可以看出,OC
的方法調用,底層變成了objc_msgSend
,也就是我們熟悉的消息發送
我們可以通過模仿objc_msgSend
方法來實現,[stu test]
的調用
從控制臺的輸出可以看到,是
一模摸一樣樣
。由此可以斷定
[stu test]
等價于objc_msgSend(stu,sel_registerName("test"))
注意
:不能直接調用
objc_msgSend
,需要導入頭文件#import <objc/message.h>
需要將
target --> Build Setting -->
搜索msg
-- 將enable strict checking of obc_msgSend calls
由YES
改為NO
,將嚴厲的檢查機制關掉,否則objc_msgSend
的參數會報錯。
- 未導入
#import <objc/message.h>
- 啟用 objc_msgSend 調用的嚴格檢查,設置為
NO
objc_msgSend
(消息的接受者,消息的主體(sel + 參數))
3.2 objc_msgSendSuper
在上面??在main
函數中調用了父類的方法[stu superTest]
,clang
編譯的源碼里面發現了objc_msgSendSuper
。
這是子類完全調用了父類的方法,那么我們子類要是也有一個
superTest
方法,但是子類并沒有實現這個方法,那么我們看看結果如何?
@interface JPPerson : NSObject
@property (nonatomic, readwrite , copy) NSString *personName;
- (void)superTest;
@end
@implementation JPPerson
- (void)superTest {
NSLog(@"%s",__func__);
}
@end
@interface JPStudent : JPPerson
@property (nonatomic, readwrite , copy) NSString *studentName;
- (void)test;
- (void)superTest;
@end
@implementation JPStudent
- (void)test {
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
JPStudent *stu = [[JPStudent alloc]init];
[stu test];
NSLog(@"-------華麗的分割線-----------");
objc_msgSend(stu,sel_registerName("test"));
[stu superTest];
}
return 0;
}
打印結果:
-[JPStudent test]
-------華麗的分割線-----------
-[JPStudent test]
-[JPPerson superTest]
Program ended with exit code: 0
對象的方法調用,實際是父類的實現方法,為了驗證這個說法,我們可以嘗試通過objc_msgSendSuper
實現驗證。
objc_msgSendSuper
方法中有兩個參數(結構體,sel),其結構體類型是objc_super
定義的結構體對象,且需要指定receiver
和super_class
兩個屬性,源碼實現定義如下
通過查看蘋果的源碼,找到了如下方法
OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
代碼改造:
int main(int argc, const char * argv[]) {
@autoreleasepool {
JPStudent *stu = [[JPStudent alloc]init];
JPPerson *person = [JPPerson alloc];
[person superTest];
struct objc_super jpsuper;
jpsuper.receiver = stu; //消息的接收者
jpsuper.super_class = [JPStudent class]; //告訴父類是誰,改成 [JPPerson class]也是一樣的
//消息的接受者還是自己 -> 父類 -> 方法么有找到請你直接找我的父親
objc_msgSendSuper(&jpsuper, sel_registerName("superTest"));
}
return 0;
}
打印結果
[26066:278406] -[JPPerson superTest]
[26066:278406] -[JPPerson superTest]
4. 總結
OC調用方法,其實本質是
發送消息
(objc_msgSend
)OC方法的調用,首先是在類中查找,如果類中沒有找到,會到類的父類中查找。
子類調用父類的方法,底層會調用
objc_msgSendSuper
更多內容持續更新
?? 請動動你的小手,點個贊????
?? 喜歡的可以來一波,收藏+關注,評論 + 轉發,以免你下次找不到我,哈哈????
??歡迎大家留言交流,批評指正,互相學習??,提升自我??