本文章轉自Objective-C 中的類和對象
Objective-C的runtime是開源的,源碼可以在蘋果官網(wǎng)下載到:objc4。
好了,下面正文:
1.id和Class的定義
runtime里面,聲明了id和Class的類型,簡化一下如下:
struct objc_class {
struct objc_class *isa;
};
struct objc_object {
struct objc_class *isa;
};
typedef struct objc_class *Class; //類 (class object)
typedef struct objc_object *id; //對象 (instance of class)
在objc中,id代表了一個對象。根據(jù)上面的聲明,凡是首地址是*isa的struct指針,都可以被認為是objc中的對象。運行時可以通過isa指針,查找到該對象是屬于什么類(Class)。
2.運行時的實現(xiàn)方式
根據(jù)上面的說法,類對象(Class)同樣也算是對象,那它的isa又是指向了什么呢?為了了解這些東西是怎么回事,這里寫一個簡單的類NyanCat,并且用C重寫一遍,看看編譯器在底層到底是如何實現(xiàn)的。
@interface NyanCat : NSObject {
int age;
NSString *name;
}
- (void)nyan;
+ (void)nyan;
@end
@implementation NyanCat
- (void)nyan1 {
printf("instance nyan~");
}
+ (void)nyan2 {
printf("class nyan~");
}
@end
上面是一個簡單的類,有兩個instance variable,有一個類方法、一個實例方法。
clang -rewrite-objc NyanCat.m
在終端執(zhí)行上面這一條語句,讓clang將該類重寫為cpp代碼,我們就能查看到大概底層的實現(xiàn)機制了(實際編譯的文件和這個會有些出入,不同目標架構和不同版本clang也會有不同..權且當參考了)。
rewrite后的代碼基本是純C的,稍微整理一下,可以提取出下面這些信息:
//Class的實際結構
struct _class_t {
struct _class_t *isa; //isa指針
struct _class_t *superclass; //父類
void *cache;
void *vtable;
struct _class_ro_t *ro; //Class包含的信息
};
//Class包含的信息
struct _class_ro_t {
unsigned int flags;
unsigned int instanceStart;
unsigned int instanceSize;
unsigned int reserved;
const unsigned char *ivarLayout;
const char *name; //類名
const struct _method_list_t *baseMethods; //方法列表
const struct _objc_protocol_list *baseProtocols; //協(xié)議列表
const struct _ivar_list_t *ivars; //ivar列表
const unsigned char *weakIvarLayout;
const struct _prop_list_t *properties; //屬性列表
};
//NyanCat(meta-class)
struct _class_t OBJC_METACLASS_$_NyanCat = {
.isa = &OBJC_METACLASS_$_NSObject,
.superclass = &OBJC_METACLASS_$_NSObject,
.cache = (void *)&_objc_empty_cache,
.vtable = (void *)&_objc_empty_vtable,
.ro = &_OBJC_METACLASS_RO_$_NyanCat, //包含了類方法等
};
//NyanCat(Class)
struct _class_t OBJC_CLASS_$_NyanCat = {
.isa = &OBJC_METACLASS_$_NyanCat, //此處isa指向meta-class
.superclass = &OBJC_CLASS_$_NSObject,
.superclass = (void *)&_objc_empty_cache,
.vtable = (void *)&_objc_empty_vtable,
.ro = &_OBJC_CLASS_RO_$_NyanCat, //包含了實例方法 ivar信息等
};
typedef struct objc_object NyanCat; //定義NyanCat類型
//更詳細的不貼代碼了..
所有NyanCat的實例的isa都指向了NyanCat(Class)。
NyanCat(Class)是一個全局變量,其中記錄了類名、成員變量信息、property信息、protocol信息和實例方法列表等。
NyanCat(Class)的isa指向了全局變量NyanCat(meta-class),meta-class里只記錄了類名、類方法列表等。
畫出圖來就是這樣:

舉例來說一下:
NyanCat *cat = [[NyanCat alloc] init];
[cat nyan1];
向cat (instance) 發(fā)送消息nyan1時,運行時會通過isa指針查找到NyanCat(Class),這里保存著本類中定義的實例方法的指針。
[NyanCat nyan2];
向NyanCat(Class)發(fā)送消息nyan2時,運行時會通過isa查找到NyanCat(meta-class),這里保存著本類中定義的類方法的指針。
運行時如何利用Class和meta-class來實現(xiàn)動態(tài)消息的,以后在記吧~
3.類的繼承
在_class_t里面,第二個成員是superclass,很明顯這個指針指向了它的父類。運行時可以通過isa和superclass獲取一個類在繼承樹上的完整信息。
為了說明方便,這里把上面的例子稍微改一下:NyanCat : Cat : NSObject 這樣一個繼承樹,畫出圖來就是這樣子的:

如上面圖中,跟隨黑線,可以看到isa的指向。運行時,每個對象的isa都不為空,這樣只要是一個id類型的對象,runtime都可以通過訪問首地址偏移(isa)來獲取該對象的信息了。
上圖中跟隨綠線,可以看到superclass的指向。當運行時在搜尋方法、ivar信息時,如果沒有找到信息,則會沿superclass的線查找上去,最終NSObject(根類)的superclass是nil。
如果自己定義了一個根類(比如NSProxy),則這個根類會替換圖中NSObject的位置。
為了驗證上面的說法,可以敲一下代碼看看:
#import "NyanCat.h"
#import <objc/runtime.h>
#import <objc/objc.h>
void test() {
NyanCat *cat = [[NyanCat alloc] init];
Class cls = object_getClass(cat); //NyanCat(Class)
class_getName(cls); //"NyanCat"
class_isMetaClass(cls); //NO
Class meta = object_getClass(cls); //NyanCat(meta-class)
class_getName(meta); //"NyanCat"
class_isMetaClass(cls); //YES
Class meta_meta = object_getClass(meta); //NSObject(meta-class)
class_getName(meta_meta); //"NSObject"
class_isMetaClass(meta_meta); //YES
}
最后吐嘈一下:平時開發(fā)時,meta-class基本是用不著接觸的,superclass指針無法訪問,isa指針可能稍后也會隱藏起來(蘋果的動作真多)。。所以上面說得這些,了解一下就好~~