1. 回顧
在之前的幾篇博客里面辕万,已經(jīng)對OC類的底層結(jié)構(gòu)進行了分析,并對內(nèi)部主要的成員變量(isa/bits)做了詳細的分析瓶盛。在上兩個博客
iOS底層探索之類的結(jié)構(gòu)—cache分析(上)
iOS底層探索之類的結(jié)構(gòu)—cache分析(下)
對類中的cache
做了比較詳細的分析带猴。后面通過斷點
查看匯編
可以發(fā)現(xiàn)在insert
方法調(diào)用流程之前观堂,還有一個cache
讀取流程,objc_msgSend
和 cache_getImp
蛤签。這就涉及到Runtime
的知識點了辞友,之前的內(nèi)容都是承上啟下的,是互相關(guān)聯(lián)的。
2. Runtime
2.1 什么是Runtime
runtime
翻譯過來稱為運行時称龙,與之對應的是編譯時
留拾。大部分的iOS開發(fā)人員,都聽過runtime
這個詞鲫尊,也知道運行時痴柔。但只是停留在表面,只是知道而已疫向,并沒有去深入的去探索和分析過竞帽。
OC
語言是一門動態(tài)語言
,擁有動態(tài)語言的三大特性:動態(tài)類型
鸿捧、動態(tài)綁定
屹篓、動態(tài)加載
。而底層實現(xiàn)就是熟悉又陌生的Runtime
匙奴。
-
運行時
是一種面向?qū)ο蟮木幊陶Z言(面向?qū)ο缶幊蹋┑倪\行環(huán)境堆巧。運行時表明了在某個時間段內(nèi),哪個程序正在運行泼菌。運行時是計算機程序運行生命周期內(nèi)的一個階段谍肤,其它階段還包括:編譯時、鏈接時和加載時哗伯。簡單理解就是荒揣, 代碼跑起來,被裝載到內(nèi)存中的過程焊刹。(你的代碼保存在磁盤上沒裝入內(nèi)存之前是個死家伙系任,只有跑到內(nèi)存中才變成活的)。 -
編譯時
顧名思義就是正在編譯的時候虐块。那什么叫編譯
呢?就是編譯器幫你把源代碼翻譯成機器能識別的二進制代碼 俩滥。 - (當然只是一般意義上這么說,實際上可能只是翻譯成某個中間狀態(tài)的語言。比如
Java
只有JVM
識別的字節(jié)碼贺奠,C#
中只有CLR
能識別的MSIL
霜旧。另外還有鏈接器、匯編器儡率、為了了便于理解我們可以統(tǒng)稱為編譯器) - 那編譯時就是簡單的作一些翻譯工作挂据,比如檢查老兄你有沒有粗心寫錯啥
關(guān)鍵字
了。 -
詞法分析
,語法分析之類的過程儿普。就像個老師檢查學生的作文中有沒有錯別字和病句一樣 崎逃。 - 如果發(fā)現(xiàn)啥錯誤編譯器就告訴你,平時使用
Xcode
時,點下build
那就開始編譯箕肃。 - 如果下面有
errors
或者warning
信息婚脱,那都是編譯器檢查出來的。這時的錯誤就叫編譯時
錯誤勺像,這個過程中做的類型檢查也就叫編譯時類型檢查
障贸,或靜態(tài)類型檢查
(所謂靜態(tài)嘛就是沒把真把代碼放內(nèi)存中運行起來,而只是把代碼當作文本來掃描下)。
2.2 runtime的使用的三種方式
runtime
的使用的三種方式吟宦,其三種實現(xiàn)方法與編譯層和底層的關(guān)系如圖所示
通過
OC
上層的代碼實現(xiàn)篮洁,例如[JPerson hello]
通過
NSObject
方法實現(xiàn),例如isKindOfClass
-
通過
Runtime API
底層方法實現(xiàn)殃姓,例如class_getInstanceSize
圖中的
compiler
就是編譯器袁波,就是我們熟悉的LLVM
3. OC方法的本質(zhì)
在之前的一篇博客iOS開發(fā)之結(jié)構(gòu)體底層探索我們知道平時寫的OC
代碼,底層實現(xiàn)其實都是C/C++
的代碼實現(xiàn)的蜗侈,再經(jīng)過編譯器LLVM
編譯篷牌,最終轉(zhuǎn)化為機器語言。
通過clang
編譯的源碼踏幻,理解了OC
對象的本質(zhì)(結(jié)構(gòu)體
)枷颊,同樣的,我們也可以使用clang
命令編譯成main.cpp
文件该面,看看方法的本質(zhì)
是什么夭苗?
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
的方法調(diào)用隔缀,底層變成了objc_msgSend
题造,也就是我們熟悉的消息發(fā)送
我們可以通過模仿objc_msgSend
方法來實現(xiàn),[stu test]
的調(diào)用
從控制臺的輸出可以看到猾瘸,是
一模摸一樣樣
界赔。由此可以斷定
[stu test]
等價于objc_msgSend(stu,sel_registerName("test"))
注意
:不能直接調(diào)用
objc_msgSend
,需要導入頭文件#import <objc/message.h>
需要將
target --> Build Setting -->
搜索msg
-- 將enable strict checking of obc_msgSend calls
由YES
改為NO
牵触,將嚴厲的檢查機制關(guān)掉仔蝌,否則objc_msgSend
的參數(shù)會報錯。
- 未導入
#import <objc/message.h>
- 啟用 objc_msgSend 調(diào)用的嚴格檢查荒吏,設置為
NO
objc_msgSend
(消息的接受者,消息的主體(sel + 參數(shù)))
3.2 objc_msgSendSuper
在上面??在main
函數(shù)中調(diào)用了父類的方法[stu superTest]
敛惊,clang
編譯的源碼里面發(fā)現(xiàn)了objc_msgSendSuper
。
這是子類完全調(diào)用了父類的方法绰更,那么我們子類要是也有一個
superTest
方法瞧挤,但是子類并沒有實現(xiàn)這個方法,那么我們看看結(jié)果如何儡湾?
@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;
}
打印結(jié)果:
-[JPStudent test]
-------華麗的分割線-----------
-[JPStudent test]
-[JPPerson superTest]
Program ended with exit code: 0
對象的方法調(diào)用特恬,實際是父類的實現(xiàn)方法,為了驗證這個說法徐钠,我們可以嘗試通過objc_msgSendSuper
實現(xiàn)驗證癌刽。
objc_msgSendSuper
方法中有兩個參數(shù)(結(jié)構(gòu)體,sel),其結(jié)構(gòu)體類型是objc_super
定義的結(jié)構(gòu)體對象显拜,且需要指定receiver
和super_class
兩個屬性衡奥,源碼實現(xiàn)定義如下
通過查看蘋果的源碼,找到了如下方法
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;
}
打印結(jié)果
[26066:278406] -[JPPerson superTest]
[26066:278406] -[JPPerson superTest]
4. 總結(jié)
OC調(diào)用方法矮固,其實本質(zhì)是
發(fā)送消息
(objc_msgSend
)OC方法的調(diào)用,首先是在類中查找譬淳,如果類中沒有找到档址,會到類的父類中查找。
子類調(diào)用父類的方法邻梆,底層會調(diào)用
objc_msgSendSuper
更多內(nèi)容持續(xù)更新
?? 請動動你的小手守伸,點個贊????
?? 喜歡的可以來一波,收藏+關(guān)注浦妄,評論 + 轉(zhuǎn)發(fā)尼摹,以免你下次找不到我,哈哈????
??歡迎大家留言交流校辩,批評指正窘问,互相學習??,提升自我??