Objective-C Runtime的數據類型


Class

Objective-C是支持反射的,先來了解一下其如何表達一個類。在Objective-C的Runtime中有個類型是Class(只在Runtime環境中使用),用來表示Objective-C中的類,其定義為:

typedef struct objc_class *Class;

可以看出,其實Class類型是一個指針,指向struct  objc_class,而struct  objc_class才是保存真正數據的地方,再看struct  objc_class的聲明(from http://www.opensource.apple.com/source/objc4/objc4-493.9/runtime/runtime.h):

struct objc_class {
    Class isa;

#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;

其中包含了方法列表、父類等信息,詳細的可以稍后再看。

 

Method

是Runtime內部定義的方法,用來代表一個方法,其聲明如下:

typedef struct objc_method *Method;

而struct  objc_method的聲明如下:

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

SEL和IMP代表什么需要看下面的內容。如果你已經了解了SEL和IMP的含義,可以看看下面這段:根據Class和Method的定義來理解Objective C中的消息機制:

先看看objc_class中method list的在runtime(http://opensource.apple.com/source/objc4/objc4-437/runtime/objc-runtime-new.h)里的定義:

typedef struct method_list_t {
    uint32_t entsize_NEVER_USE;  // low 2 bits used for fixup markers
    uint32_t count;
    struct method_t first;
} method_list_t;
typedef
struct method_t { SEL name; const char *types; IMP imp; } method_t;

SEL相當於char*,可以認為objc_class中method list保存了一個SEL<->IMP的映射,看下面的代碼:

Bird * aBird = [[Bird alloc] init];

[aBird fly];

其中對fly的調用,其實是由編譯器插入了一些代碼,根據SEL([aBird fly] 中的fly就是SEL)找到了IMP,從而進行調用的。下面看編譯器插入了什么樣的代碼。我們來看Objective C runtime中跟msg相關的函數:

id objc_msgSend(id theReceiver, SEL theSelector, ...)

這個函數發送消息給theReceiver,並將返回值返回。編譯器其實就是將[aBird fly]轉化成了對objc_msgSend的調用,從而實現消息機制的。objec_msgSend()函數將會使用theReceiver的isa指針來找到theReceiver的類空間結構並在類空間結構中查找theSelector所對應的方法。如果沒有找到,那么將使用指向父類的指針找到父類空間結構進行theSelector的查找。如果仍然沒有找到,就繼續往父類的父類一直找,直到找到為止。如果找不到怎么辦呢?關於消息機制,有一篇引用文章,介紹的更加詳細,這里就不贅述。

 

Ivar

Runtime中用來表示instance variable(實例變量,跟某個對象關聯,不能被靜態方法使用,與之想對應的是class variable),其聲明如下:

typedef struct objc_ivar *Ivar;

而struct objc_ivar的聲明如下:

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
}                                                            OBJC2_UNAVAILABLE;

Category

Runtime中用來表示Category( link ?),其聲明為:

typedef struct objc_category *Category;

struct objc_category 的定義也在runtime.h文件中。

[]Catagory可以動態地為已經存在的類添加新的行為。這樣可以保證類的原始設計規模較小,功能增加時再逐步擴展。使用Category對類進行擴展時,不需要訪問其源代碼,也不需要創建子類。Category使用簡單的方式,實現了類的相關方法的模塊化,把不同的類方法分配到不同的分類文件中。下面看一個例子:

SomeClass.h
@interface SomeClass : NSObject{
}
-(void) print;
@end 

這是類SomeClass的聲明文件,其中包含一個實例方法print。如果我們想在不修改原始類、不增加子類的情況下,為該類增加一個hello的方法,只需要簡單的定義兩個文件SomeClass+Hello.h和SomeClass+Hello.m,在聲明文件和實現文件中用“()”把Category的名稱括起來即可。聲明文件代碼如下:

#import "SomeClass.h"
 
@interface SomeClass (Hello)
-(void)hello;
@end

實現文件代碼如下:

#import "SomeClass+Hello.h"
@implementationSomeClass (Hello)
-(void)hello{
    NSLog (@"name:%@ ", @"Jacky");
}
@end

其中Hello是Category的名稱,如果你用XCode創建Category,那么需要填寫的內容包括名稱和要擴展的類的名稱。這里還有一個約定成俗的習慣,將聲明文件和實現文件名稱統一采用“原類名+Category”的方式命名。
調用也非常簡單,毫無壓力,首先引入Category的聲明文件,然后正常調用即可:

#import "SomeClass+Hello.h"
 
SomeClass * sc =[[SomeClass alloc] init];
[sc hello] 

執行結果是:
name:Jacky

Category的使用場景:
1、當你在定義類的時候,在某些情況下(例如需求變更),你可能想要為其中的某個或幾個類中添加方法。
2、一個類中包含了許多不同的方法需要實現,而這些方法需要不同團隊的成員實現
3、當你在使用基礎類庫中的類時,你可能希望這些類實現一些你需要的方法。

遇到以上這些需求,Category可以幫助你解決問題。當然,使用Category也有些問題需要注意,
1、Category可以訪問原始類的實例變量,但不能添加變量,如果想添加變量,可以考慮通過繼承創建子類。
2、Category可以重載原始類的方法,但不推薦這么做,這么做的后果是你再也不能訪問原來的方法。如果確實要重載,正確的選擇是創建子類。
3、和普通接口有所區別的是,在分類的實現文件中可以不必實現所有聲明的方法,只要你不去調用它。

用好Category可以充分利用Objective-C的動態特性,編寫出靈活簡潔的代碼。

 

SEL

Runtime中用來表示一個method selector,其聲明為:

typedef struct objc_selector     *SEL;

沒有找到struct objc_selector的定義,有人說是編譯器定義的,GCC 和MacOSX的實現方式還不一樣,不想花時間找GCC的代碼,而且也沒那么重要,所以就先姑且相信這個說法吧。

IMP

IMP是一個函數指針,指向方法的實現,其定義為:

id (*IMP)(id, SEL, ...)

其所指向的方法,返回一個id(Cocoa 對象),需要傳入的第一個參數是self(指向某個對象,或者一個類),第二個參數是方法的SEL。

objc_property_t

objc_method_list

objc_cache

objc_protocol_list

id

在 Objective-C中id類型的對象可以轉換為任何一種對象,有點類似與void *指針類型的作用。下面簡要介紹一下id類型。

id標志符:通用對象類型。id類型是一個獨特的數據類型,可以轉換為任何數據類型,即id類型的變量可以存放任何數據類型的對象。id在objc.h中的定義為:

typedef struct objc_object {
    Class isa;
} *id;

從上面的介紹,我們已經知道Class是struct  objc_class的指針別名,所以id可以指向一個第一個元素是Class的struct;那么它為什么可以指向NSObject對象呢?下面看NSObject的定義:

@interface NSObject <NSObject> {
    Class    isa;
}

可以看出NSObject的第一個對象是Class類型的isa。因為第一個元素相同,也就意味着可以互相cast而不損失信息,下面是用C語言來演示的其實現原理:

#include <stdio.h>
#include <stdlib.h>
struct objc_class
{
    int count;
    char * name;
};

typedef struct objc_class * Class;
typedef struct objc_obj0
{
    Class isa;
}*id;

typedef struct objc_obj1
{
    Class isa;
    int a;
}*id1;

typedef struct objc_obj2
{
    Class isa;
    char *b;
}*id2;

int main(int argc, char **argv)
{
    // id 的第一個元素與id1是一樣的,所以可以用id指向id1的元素,而不損失任何信息,不過后續使用的時候應該使用其實際類型
    id a = (struct objc_obj1 *)malloc(sizeof(struct objc_obj1)); 
    id b = (struct objc_obj1 *)malloc(sizeof(struct objc_obj1)); 
}

實施上,通常而言,這樣使用時編譯器是要report warning的,我們可以在.m文件中加入下面的代碼來驗證:

typedef struct objc_object *id2;


id2 = [[NSNumber alloc] initWithInt:(i*3)];

這時是會報incompatible pointer types initializing 'id2' (aka 'struct objc_object *') with an expression of type 'NSNumber *' 的,但id2和id的定義相同,為什么使用id時不會有這個warning呢?因為編譯器對id做了特殊處理,不報warning。這下對id有了更多了解了吧。后續而來的問題就是,為什么可以在id類型上調用一些NSNumber上才有的方法呢?這一部分留到Dynamic Typing and Dynamic binding時再說吧。 

 

http://unixjunkie.blogspot.com/2008/03/id-vs-nsobject-vs-id.html

http://www.cppblog.com/kesalin/archive/2011/08/15/objc_message.html

http://www.cnblogs.com/chijianqiang/archive/2012/06/22/objc-category-protocol.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM