標籤:blog http 使用 strong 檔案 資料
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