ios runtime基礎知識

非盈利無廣告開發者專用網址導航:www.dev666.com編程

前言

學習Objective-C的運行時Runtime系統是頗有必要的。我的以爲,得之可得天下,失之則失天下。數組

Objective-C提供了編譯運行時,只要有可能,它均可以動態地運做。這意味着不只須要編譯器,還須要運行時系統執行編譯的代碼。運行時系統充當Objective-C語言的操做系統,有了它才能運做。緩存

運行時系統所提供功能是很是強大的,在實際開發中是常用到的。好比,蘋果不容許咱們給Category追加擴展屬性,是由於它不會自動生成成員變量,那麼咱們經過運行時就能夠很好的解決這個問題。另外,常見的模型轉字典或者字典轉模型,對象歸檔等。後續咱們再來學習如何應用,本節只是講講理論。微信

與Runtime交互

Objective-C程序有有三種與runtime系統交互的級別:數據結構

  1. 經過Objective-C源代碼
  2. 經過Foundation庫中定義的NSObject提供的方法
  3. 經過直接調用runtime方法

經過Objective-C源代碼

在大多數的部分,運行時系統會自動運行並在後臺運行。咱們使用它只是寫源代碼並編譯源代碼。當編譯包含Objective-C類和方法的代碼時,編譯器會建立實現了語言動態特性的數據結構和函數調用。該數據結構捕獲在類、擴展和協議中所定義的信息。函數

最重要的runtime函數是發消息函數,在編譯時,編譯器會轉換成相似objc_msgSend這樣的發送消息的函數。所以,咱們經過寫好源代碼,編譯器會自動幫助咱們編譯成runtime代碼。學習

經過NSObject提供的方法

在Cocoa編程中,大部分的類都繼承於NSObject,也就是說NSObject一般是根類,大部分的類都繼承於NSObject。有些特殊的狀況下,NSObject只是提供了它應該要作什麼的模板,卻沒有提供全部必須的代碼。this

有些NSObject提供的方法僅僅是爲了查詢運動時系統的相關信息,這此方法均可以反查本身。好比-isKindOfClass:和-isMemberOfClass:都是用於查詢在繼承體系中的位置。-respondsToSelector:指明是否接受特定的消息。+conformsToProtocol:指明是否要求實如今指定的協議中聲明的方法。-methodForSelector:提供方法實現的地址。編碼

經過直接調用runtime函數

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:其它普通的消息都會經過該函數來發送也就是說,咱們是經過編譯器來自動轉換成運行時代碼時,它會根據類型自動轉換成下面的其它一個函數:

  • objc_msgSend_stret:消息中須要有數據結構做爲返回值時,會經過該函數來發送消息並接收返回值
  • objc_msgSendSuper:與objc_msgSend函數相似,只是它把消息發送給父類實例
  • objc_msgSendSuper_stret:與objc_msgSend_stret函數相似,只是它把消息發送給父類實例並接收數組結構做爲返回值

另外,若是函數返回值是浮點類型,官方說明以下:

* 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 ...這樣的崩潰提示了。

Message Forwarding

當發送消息給一個不處理該消息的對象是錯誤的。而後在宣佈錯誤以前,運行時系統給了接收消息的對象處理消息的第二個機會。

當某對象不處理某消息時,能夠經過重寫-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成員。

 

Type Encoding

編碼值

含意

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)

咱們想要經過運行時處理各類類型,那麼咱們必需要知道哪一種字符表明什麼類型。

更多內容與學習交流請關注我的微信公衆帳號:極客峯

相關文章
相關標籤/搜索