學習Objective-C
的運行時Runtime
系統是頗有必要的。我的以爲,得之可得天下,失之則失天下。編程
Objective-C
提供了編譯運行時,只要有可能,它均可以動態地運做。這意味着不只須要編譯器,還須要運行時系統執行編譯的代碼。運行時系統充當Objective-C
語言的操做系統,有了它才能運做。數組
運行時系統所提供功能是很是強大的,在實際開發中是常用到的。好比,蘋果不容許咱們給Category
追加擴展屬性,是由於它不會自動生成成員變量,那麼咱們經過運行時就能夠很好的解決這個問題。另外,常見的模型轉字典或者字典轉模型,對象歸檔等。後續咱們再來學習如何應用,本節只是講講理論。緩存
Objective-C
程序有有三種與runtime
系統交互的級別:數據結構
Objective-C
源代碼Foundation
庫中定義的NSObject
提供的方法runtime
方法在大多數的部分,運行時系統會自動運行並在後臺運行。咱們使用它只是寫源代碼並編譯源代碼。當編譯包含Objective-C
類和方法的代碼時,編譯器會建立實現了語言動態特性的數據結構和函數調用。該數據結構捕獲在類、擴展和協議中所定義的信息。app
最重要的runtime
函數是發消息函數,在編譯時,編譯器會轉換成相似objc_msgSend
這樣的發送消息的函數。所以,咱們經過寫好源代碼,編譯器會自動幫助咱們編譯成runtime
代碼。函數
在Cocoa
編程中,大部分的類都繼承於NSObject
,也就是說NSObject
一般是根類,大部分的類都繼承於NSObject
。有些特殊的狀況下,NSObject
只是提供了它應該要作什麼的模板,卻沒有提供全部必須的代碼。學習
有些NSObject
提供的方法僅僅是爲了查詢運動時系統的相關信息,這此方法均可以反查本身。好比-isKindOfClass:
和-isMemberOfClass:
都是用於查詢在繼承體系中的位置。-respondsToSelector:
指明是否接受特定的消息。+conformsToProtocol:
指明是否要求實如今指定的協議中聲明的方法。-methodForSelector:
提供方法實現的地址。this
runtime
庫函數在usr/include/objc
目錄下,咱們主要關注是這兩個頭文件:編碼
1
2
3
4
|
#import <objc/runtime.h>
#import <objc/objc.h>
|
關於如何使用,後續的文章再細細講解。spa
爲何叫消息呢?由於面向對象編程中,對象調用方法叫作發送消息。在編譯時,應用的源代碼就會被編將對象發送消息轉換成runtime
的objc_msgSend
函數調用。
在Objective-C
,消息在運行時並不要求實現。編譯器會轉換消息表達式:
1
2
3
|
[receiver message];
|
在編譯時會轉換成相似這樣的函數調用:
1
2
3
|
objc_msgSend(receiver, selector);
|
具體會轉換成哪一個,咱們來看看官方的原話:
1
2
3
4
5
6
7
|
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.
|
也就是說,咱們是經過編譯器來自動轉換成運行時代碼時,它會根據類型自動轉換成下面的其它一個函數:
另外,若是函數返回值是浮點類型,官方說明以下:
1
2
3
4
5
6
7
8
9
|
* 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`.
|
其實這是一個條件編譯,咱們不用擔憂是哪一種處理器上,咱們只須要調用objc_msgSend_fpret
函數便可。
注意事項:必定要調用所調用的API支持哪些平臺,亂調在致使部分平臺上不支持而崩潰的。
當消息被髮送到實例對象時,它是如何處理的:
咱們的根類是NSObject
,它會一層一層的傳遞,直接找到要處理該消息的對象,若都沒有找到,正常狀況下會出現Unreconized selector ...
這樣的崩潰提示了。
當發送消息給一個不處理該消息的對象是錯誤的。而後在宣佈錯誤以前,運行時系統給了接收消息的對象處理消息的第二個機會。
當某對象不處理某消息時,能夠經過重寫-forwardInvocation:
方法來提供一個默認的消息響應或者避免出錯。當對象中找不到方法實現時,會按照類繼承關係一層層往上找。咱們看看類繼承關係圖:
全部元類中的isa
指針都指向根元類,而根元類的isa
指針則指向自身。根元類是繼承於根類的,與根類的結構體成員一致,都是objc_class結構體,不一樣的是根元類的isa
指針指向自身,而根類的isa
指針爲nil
咱們再看看消息處理流程:
當對象查詢不到相關的方法,消息得不到該對象處理,會啓動「消息轉發」機制。消息轉發還分爲幾個階段:先詢問receiver
或者說是它所屬的類是否能動態添加方法,以處理當前這個消息,這叫作「動態方法解析」,runtime會經過+resolveInstanceMethod:
判斷可否處理。若是runtime完成動態添加方法的詢問以後,receiver
仍然沒法正常響應則Runtime會繼續向receiver詢問是否有其它對象即其它receiver能處理這條消息,若返回可以處理的對象,Runtime會把消息轉給返回的對象,消息轉發流程也就結束。若無對象返回,Runtime會把消息有關的所有細節都封裝到NSInvocation
對象中,再給receiver
最後一次機會,令其設法解決當前還未處理的這條消息。
提示:消息處理越日後,開銷也就會越大,所以最好直接在第一步就能夠獲得消息處理。
咱們看看類結構體:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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 *` */
|
咱們能夠看到每一個類結構體都會有一個isa
指針,它是指向元類的。它還有一個父類指針super_class
,指針父類。包含了類的名稱name
、類的版本信息version
、類的一些標識信息info
、實例大小instance_size
、成員變量地址列表ivars
、方法地址列表methodLists
、緩存最近使用的方法地址cache
、協議列表protocols`。
咱們在使用時,常用到Class
,它就是:
1
2
3
|
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) |
咱們想要經過運行時處理各類類型,那麼咱們必需要知道哪一種字符表明什麼類型。