非盈利無廣告開發者專用網址導航:www.dev666.com編程
學習Objective-C的運行時Runtime系統是頗有必要的。我的以爲,得之可得天下,失之則失天下。數組
Objective-C提供了編譯運行時,只要有可能,它均可以動態地運做。這意味着不只須要編譯器,還須要運行時系統執行編譯的代碼。運行時系統充當Objective-C語言的操做系統,有了它才能運做。緩存
運行時系統所提供功能是很是強大的,在實際開發中是常用到的。好比,蘋果不容許咱們給Category追加擴展屬性,是由於它不會自動生成成員變量,那麼咱們經過運行時就能夠很好的解決這個問題。另外,常見的模型轉字典或者字典轉模型,對象歸檔等。後續咱們再來學習如何應用,本節只是講講理論。微信
Objective-C程序有有三種與runtime系統交互的級別:數據結構
在大多數的部分,運行時系統會自動運行並在後臺運行。咱們使用它只是寫源代碼並編譯源代碼。當編譯包含Objective-C類和方法的代碼時,編譯器會建立實現了語言動態特性的數據結構和函數調用。該數據結構捕獲在類、擴展和協議中所定義的信息。函數
最重要的runtime函數是發消息函數,在編譯時,編譯器會轉換成相似objc_msgSend這樣的發送消息的函數。所以,咱們經過寫好源代碼,編譯器會自動幫助咱們編譯成runtime代碼。學習
在Cocoa編程中,大部分的類都繼承於NSObject,也就是說NSObject一般是根類,大部分的類都繼承於NSObject。有些特殊的狀況下,NSObject只是提供了它應該要作什麼的模板,卻沒有提供全部必須的代碼。this
有些NSObject提供的方法僅僅是爲了查詢運動時系統的相關信息,這此方法均可以反查本身。好比-isKindOfClass:和-isMemberOfClass:都是用於查詢在繼承體系中的位置。-respondsToSelector:指明是否接受特定的消息。+conformsToProtocol:指明是否要求實如今指定的協議中聲明的方法。-methodForSelector:提供方法實現的地址。編碼
runtime庫函數在usr/include/objc目錄下,咱們主要關注是這兩個頭文件:spa
#import <objc/runtime.h> #import <objc/objc.h>
消息(Message)關於如何使用,後續的文章再細細講解。
爲何叫消息呢?由於面向對象編程中,對象調用方法叫作發送消息。在編譯時,應用的源代碼就會被編將對象發送消息轉換成runtime的objc_msgSend函數調用。
在Objective-C,消息在運行時並不要求實現。編譯器會轉換消息表達式:
[receiver message];
在編譯時會轉換成相似這樣的函數調用:
objc_msgSend(receiver, selector);
具體會轉換成哪一個,咱們來看看官方的原話:
When it encounters a method call, the compiler generates a call to one of the * functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret. * Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper; * other messages are sent using \c objc_msgSend. Methods that have data structures as return values * are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.
objc_msgSend:其它普通的消息都會經過該函數來發送也就是說,咱們是經過編譯器來自動轉換成運行時代碼時,它會根據類型自動轉換成下面的其它一個函數:
另外,若是函數返回值是浮點類型,官方說明以下:
* arm: objc_msgSend_fpret not used * i386: objc_msgSend_fpret used for `float`, `double`, `long double`. * x86-64: objc_msgSend_fpret used for `long double`. * * arm: objc_msgSend_fp2ret not used * i386: objc_msgSend_fp2ret not used * x86-64: objc_msgSend_fp2ret used for `_Complex long double`.
注意事項:必定要調用所調用的API支持哪些平臺,亂調在致使部分平臺上不支持而崩潰的。其實這是一個條件編譯,咱們不用擔憂是哪一種處理器上,咱們只須要調用objc_msgSend_fpret函數便可。
當消息被髮送到實例對象時,它是如何處理的:
咱們的根類是NSObject,它會一層一層的傳遞,直接找到要處理該消息的對象,若都沒有找到,正常狀況下會出現Unreconized selector ...這樣的崩潰提示了。
當發送消息給一個不處理該消息的對象是錯誤的。而後在宣佈錯誤以前,運行時系統給了接收消息的對象處理消息的第二個機會。
當某對象不處理某消息時,能夠經過重寫-forwardInvocation:方法來提供一個默認的消息響應或者避免出錯。當對象中找不到方法實現時,會按照類繼承關係一層層往上找。
全部元類中的isa指針都指向根元類,而根元類的isa指針則指向自身。根元類是繼承於根類的,與根類的結構體成員一致,都是objc_class結構體,不一樣的是根元類的isa指針指向自身,而根類的isa指針爲nil
當對象查詢不到相關的方法,消息得不到該對象處理,會啓動「消息轉發」機制。消息轉發還分爲幾個階段:先詢問receiver或者說是它所屬的類是否能動態添加方法,以處理當前這個消息,這叫作「動態方法解析」,runtime會經過+resolveInstanceMethod:判斷可否處理。若是runtime完成動態添加方法的詢問以後,receiver仍然沒法正常響應則Runtime會繼續向receiver詢問是否有其它對象即其它receiver能處理這條消息,若返回可以處理的對象,Runtime會把消息轉給返回的對象,消息轉發流程也就結束。若無對象返回,Runtime會把消息有關的所有細節都封裝到NSInvocation對象中,再給receiver最後一次機會,令其設法解決當前還未處理的這條消息。
提示:消息處理越日後,開銷也就會越大,所以最好直接在第一步就能夠獲得消息處理。
咱們看看類結構體:
struct objc_class { Class isa OBJC_ISA_AVAILABILITY; #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; const char *name OBJC2_UNAVAILABLE; long version OBJC2_UNAVAILABLE; long info OBJC2_UNAVAILABLE; long instance_size OBJC2_UNAVAILABLE; struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; struct objc_method_list **methodLists OBJC2_UNAVAILABLE; struct objc_cache *cache OBJC2_UNAVAILABLE; struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE; /* Use `Class` instead of `struct objc_class *` */
咱們在使用時,常用到Class,它就是:咱們能夠看到每一個類結構體都會有一個isa指針,它是指向元類的。它還有一個父類指針super_class,指針父類。包含了類的名稱name、類的版本信息version、類的一些標識信息info、實例大小instance_size、成員變量地址列表ivars、方法地址列表methodLists、緩存最近使用的方法地址cache、協議列表protocols`。
typedef struct objc_class *Class;
當類爲根類時,它的super_class就會是nil。普通的Class存儲的是實例成員,如-號方法、屬性、成員變量,而isa則指向元類,而元類存儲的是靜態成員,如+號方法、static成員。
編碼值 |
含意 |
c |
表明char類型 |
i |
表明int類型 |
s |
表明short類型 |
l |
表明long類型,在64位處理器上也是按照32位處理 |
q |
表明long long類型 |
C |
表明unsigned char類型 |
I |
表明unsigned int類型 |
S |
表明unsigned short類型 |
L |
表明unsigned long類型 |
Q |
表明unsigned long long類型 |
f |
表明float類型 |
d |
表明double類型 |
B |
表明C++中的bool或者C99中的_Bool |
v |
表明void類型 |
* |
表明char *類型 |
@ |
表明對象類型 |
# |
表明類對象 (Class) |
: |
表明方法selector (SEL) |
[array type] |
表明array |
{name=type…} |
表明結構體 |
(name=type…) |
表明union |
bnum |
A bit field of num bits |
type |
A pointer to type |
? |
An unknown type (among other things, this code is used for function pointers) |
咱們想要經過運行時處理各類類型,那麼咱們必需要知道哪一種字符表明什麼類型。