Runtime理解

動態語言緩存

OC是一門徹徹底底的動態語言,因此它的不少機制都是動態運行時決定的。這點和C語言不同,C語言是靜態綁定,也就是編譯後全部的一切都已經決定了。這一點和C語言的函數指針有些相似,不少時候函數指針在編譯的時候並不知道會指向哪一個函數,因此此時就是動態綁定。安全

舉幾個OC動態類型的例子,最爲直接的就是id類型了、還有關聯對象、動態綁定、消息轉發、方法調配、這些技術都是動態類型很好的證實函數

 

OC對象結構優化

在介紹動動態性以前,咱們先來看看OC對象的一些結構。ui

#import<objc/runtime>這是OC運行時函數庫,裏面定義了不少結構體。spa

首先看對象的結構:設計

typdef struct objc_object {
      Class isa;     
}  *id;

對象結構中很是簡單,只有一個isa指針,isa指針後面咱們會介紹。指針

接下來咱們看類的結構體code

複製代碼
struct objc_class {
    Class isa

#if !__OBJC2__
    Class super_class                                        
    const char *name                                       
    long version                                           
    long info                                               
    long instance_size                                       
    struct objc_ivar_list *ivars                           
    struct objc_method_list **methodLists          
    struct objc_cache *cache                             
    struct objc_protocol_list *protocols               
#endif

}
typedef struct objc_class *Class;
複製代碼

能夠看到不少信息都在Class中定義着,裏面信息以下對象

字段 含義
isa isa指針
super_class 父類指針
name 類名
version 類的版本信息,默認爲0
info 供運行期使用的一些位標識
instance_size 實例的大小
ivars 實例變量列表
methodLists 方法列表
cache 指向最近調用的方法,用於優化調用方法的速度
protocols 協議列表

 

 

 

 

 

 

 

 

 

 

接下來咱們逐個介紹一下:

 

isa指針和super_class

在OC中,嚴格意義上講是沒有類這種概念的,每個類都是一個對象,只不過類對象是一個單例。

isa指針存在於每個對象中,類普通實例的isa指針指向類,類的isa指針指向它的元類(類方法所有都在元類中存放)。元類的isa指針指向根元類,也就是NSObject的isa所指向的元類。

super_class只有類和元類纔有,它們分別指向本身的父類和父元類,而爲了讓NSObject成爲全部類的根類,讓NSObject的元類的父類指針也指向了NSObject。

這樣說可能也不是很好理解,看下面這張圖應該就很快理解了。

等下咱們說到消息傳遞的時候還會在說到isa和super_class。

ivars屬性列表

複製代碼
struct objc_ivar_list {
    int ivar_count                       
#ifdef __LP64__
    int space                                       
#endif
    /* variable length structure */
    struct objc_ivar ivar_list[1]               
}    
複製代碼

space做用還不太清楚...求指教啊。

下面是實例變量結構

複製代碼
struct objc_ivar {
    char *ivar_name                                          OBJC2_UNAVAILABLE;
    char *ivar_type                                          OBJC2_UNAVAILABLE;
    int ivar_offset                                          OBJC2_UNAVAILABLE;
#ifdef __LP64__
    int space                                                OBJC2_UNAVAILABLE;
#endif
}  
複製代碼

能夠看到有一個ivar_offset,這個是實例變量在編譯時的偏移量,是由編譯時決定的。

 methodLists方法列表

複製代碼
struct objc_method_list {
    struct objc_method_list *obsolete                       

    int method_count                                       
#ifdef __LP64__
    int space                                            
#endif
    /* variable length structure */
    struct objc_method method_list[1]                  
}  
複製代碼

該結構有方法鏈表和方法總數。

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}       

裏面有函數名,返回值類型和函數實現,接下來看看SEL和IMP定義

typedef struct objc_selector *SEL;
typedef id (*IMP)(id, SEL, ...); 

能夠看到SEL是objc_selector,(*IMP)(id,SEL,...)是id,我沒有找到objc_selector的結構因此這裏也無法說什麼...

cache緩存列表

複製代碼
typedef struct objc_cache *Cache                            

#define CACHE_BUCKET_NAME(B)  ((B)->method_name)
#define CACHE_BUCKET_IMP(B)   ((B)->method_imp)
#define CACHE_BUCKET_VALID(B) (B)
#ifndef __LP64__
#define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))
#else
#define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>3)) & (mask))
#endif
struct objc_cache {
    unsigned int mask /* total = mask + 1 */             
    unsigned int occupied                                 
    Method buckets[1]                                   
};
複製代碼

 

 緩存列表裏面包含了已緩存的方法,用於快速的調用,不須要在去方法列表裏面查詢了。

protocol協議列表

struct objc_protocol_list {
    struct objc_protocol_list *next;
    long count;
    Protocol *list[1];
};

 

協議列表包含了協議數量和協議的指針。

 

id

id類型實際上就是一個objc_object的typedef,是一個對象實例,並且仍是一個指針,因此用id來定義對象的時候就不須要加*號了。

id類型每每須要咱們使用」自省「機制來保證使用安全,所謂自省其實就是看看這個對象是否是某個類的實例,或者是否是其子類。

自省用一下兩個方法:

isKindOfClass:(Class)class          判斷是否是其類族對象

isMemberOfClass:(Class)class     判斷是否是類自己對象

 

關聯對象

關聯對象在我以前的博客中已經有介紹了,這裏就再也不說了。

 

OC消息機制和消息轉發

別的語言調用函數,OC則叫作發送消息,這是由於全部OC的方法調用實際上底層都是經過

objc_msgSend(id self , SEL cmd,...)來發送的。

該函數的做用就是傳遞給一個對象某個方法,後面的不定參數列表是方法所須要的參數。

這裏說一下OC的消息傳遞機制,首先對一個對象發送消息,它會先檢查本身的類中有沒有該方法,若是沒有就找他的父類中有沒有,若是尚未則會進行消息轉發。

在看例子以前先說一下,Xcode6貌似默認行爲不讓咱們使用objc_msgSend了,因此須要先設置一下

 

把這一項設置爲No就能夠了。

這裏看個例子,

複製代碼
        EqualObject *object1 = [EqualObject new];
        EqualObject *object2 = [EqualObject new];
        object1.name = @"xiaoming";
        object2.name = @"xiaoming";
        
        BOOL isEqual = objc_msgSend(object1,@selector(isEqualToEqualObject:),object2);
        
        if(isEqual)
        {
            NSLog(@"equal");
        }
複製代碼

 

EqualObject是咱們本身實現的類,它有一個判斷是否相等的方法isEqualToEqualObject:,若是name相等就人爲兩個對象相等。

這裏咱們直接傳遞消息,不經過OC語法,運行程序能夠看到equal被打印了出來。

而後咱們再看看消息轉發機制。

當該對象包括其父類都沒有這個方法的時候會啓動,消息轉發機制分爲兩大階段。

第一階段先看對象所屬類是否有能力動態添加方法,已處理這個位置的選擇子,這叫作動態解析(dynamic method resolution)。

第二階段設計「完整的消息轉發機制」。若是運行期系統已經把第一階段執行完了,那麼接受者本身就無法再以動態新增方法的手段來處理與消息相關的方法調用。這又分爲兩個小步。

首先,請接受者看看有沒有其餘對象能處理這條消息。如有,則在運行時轉給那個對象,因而消息轉發過程結束。若沒有「備用的接受者」,則啓動完成的消息轉發機制,運行起系統會把與消息有關的所有細節都封裝到NSInvocation對象中,再給接受者最後一次機會,令其設法解決當前還未處理的這條消息。

 

動態方法解析

void showLog(id self, SEL _cmd, id value)
{
    if([value isKindOfClass:[NSString class]])
    {
        NSLog(@"%@",(NSString *)value);
    }
}

+(BOOL)resolveInstanceLog:(SEL)sel
{
    NSString *selString = NSStringFromSelector(sel);
    if([selString isEqualToString:@"showMessage:"])
    {
        class_addMethod(self, sel, (IMP)showMessage, "v@:@");
        return YES;
    }
    else
    {
        return [super resolveInstanceMethod:sel];
    }
}

遇到沒法解析的信息後,首先將調用其所屬類的下列類方法:
+(BOOL)resolveInstanceMethod:(SEL)selector

該方法參數就是未知的選擇子,返回BOOL類型那個,表示這個類是否能新增一個實力方法已處理這個選擇子。假如是類方法,那麼會調用

+(BOOL)resolveClassMethod:(SEL)selector

使用這種方法的前提是相關的實現已經寫好了,只等運行時動態的插入就行,好比CoreData中NSManagedObjects對象的屬性時就能夠這麼作,由於實現這些屬性所需的存取方法在編譯期就能肯定。

下面咱們看個例子

相關文章
相關標籤/搜索