iOS開發中的runtime被稱爲黑魔法,本節就向大家講解什麼是runtime.
runtime 是屬於OC底層的實現,可以進行一些OC無法操作的事情
1.利用Runtime,在程序運行時,動態加載一個類
2.利用Runtime,在程序運行時,動態修改類的屬性,方法
3.利用Runtime遍歷一個類的所有屬性
……(總之很強大)
接下來我會一步一步展示下什麼是runtime,
一、首先配置下測試環境
首先我們新建一個工程,X-code5.0之後蘋果不建議使用底層代碼,如果使用runtime方法,需要在buildSetting中做修改。如下所示:將objc_msgSend檢測設爲禁用。
接着我們新建一個Person類,聲明並實現eat方法:
二、我們先看下傳統的方法調用都有哪些:
我們可以使用傳統的方法調用如下,相信大家都經常用到
Person *person = [[Person alloc] init];
// 1.OC語法的方法調用。
[person eat];
// 2.用performSelector調用
[person performSelector:@selector(eat)];
三、接下來我們使用runtime來調用
引入頭文件:#import < objc/message.h> 或 #include < objc/runtime.h> 前者包括後者。
Person *person = [[Person alloc] init];
// 3.使用runtime調用
objc_msgSend(person, @selector(eat));
同樣打印信息如下:
2017-04-21 14:34:28.891 Runtime[38675:3227119] enter person eat
我們詳解下:
objc_msgSend(<#id self#>, <#SEL op, ...#>)
我們OC的方法調用的消息發送機制可以在這裏得到答案,每一個方法,其實都是對一個對象發送一個消息。
id self : 發送消息的對象。
<#SEL op, …#>:發送什麼消息,後面…就代表可擴展參數,如果方法需要入參的話可做擴展,就像NSLog(<#NSString * _Nonnull format, …#>)擴展參數用法一樣。
另外:@selector方法選擇器在runtime時被替換成:sel_registerName
所以方法進一步底層化爲:
objc_msgSend(person, sel_registerName("init"));
四、msgSend還可以做哪些事?
上面一個簡單的展示,可以用這個方法來代替方法調用,支持方法有多個入參,那麼
Person *person = [[Person alloc] init]
可以使用類似方法嗎?答案是可以的。
我們一步一步拆分得到:
1、
Person *person = [Person alloc];
我們可以使用runtime的另一個objc_getClass(“Person”),得到類的名字,再調用alloc:
objc_msgSend(objc_getClass("Person"), @selector(alloc));
2、
[person init];
我們可以直接這樣做:
objc_msgSend(person, @selector(“init”));
總結整理後我們可以吧person的初始化方法替換爲runtime語言:
objc_msgSend(objc_getClass("Person"), sel_registerName("alloc"),sel_registerName("init"));
五、編譯文件,查看真實的runtime
讀者可能會問:我該怎麼證明這些都是正確的呢?相信看完下面就明白了。
1、我們新建一個Command Line工程,注意一定要是這樣的工程,因爲這裏不會因人UIKit框架,筆者能力有限,暫不會編譯那種文件。
2、新建一個Person類,同樣聲明實現eat方法:
3、我們在控制檯用clang指令編譯main.m文件:
4、在同級目錄生成了個main.cpp文件,這個就是我們想要的:
5、打開main.cpp我們看文件的最下邊:
怎麼樣,相信了吧,我們的每個方法在編譯時候都會被編譯成runtime運行時語言,他能幫助我們做很多強大功能。
接下來在後面博客中,我會使用實際案例,幫助大家更深刻的理解runtime。