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