OC對象的分類
OC對象可以分為三類墩瞳,分別是實例對象驼壶,類對象,元類對象喉酌。
實例對象(instance對象)
instance對象是通過類alloc出來的對象热凹,每次調(diào)用alloc都會產(chǎn)生新的instance對象。
- instance對象在內(nèi)存中存儲的信息
isa指針(因為幾乎所有的對象都繼承自NSObject泪电,而NSObject對象的結(jié)構(gòu)體重就是一個isa指針)
其他成員變量(注意這里存儲的是成員變量的值)
我們看一下Demo:
@interface Person:NSObject
{
@public
int _age;
}
@property (nonatomic, assign)int height;
@end
@implementation Person
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
Person *p1 = [[Person alloc] init];
p1->_age = 3;
Person *p2 = [[Person alloc] init];
p2->_age = 4;
return 0;
}
}
那么這個時候首先為p1實例對象分配存儲空間般妙,先存儲isa這個指針,然后存儲成員變量_age=3;對于p2實例對象也是一樣的相速。由于p1指針指向Person實例對象碟渺,也就是指向Person實例對象在內(nèi)存中的首地址,而Person實例對象中存儲的第一個成員變量是isa指針突诬,所以p1指針指向的地址就是存儲isa指針的地址苫拍。
類對象(Class對象)
類對象的獲取方式:
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = object_getClass(object1);
Class objectClass4 = object_getClass(object2);
Class objectClass5 = [NSObject class];
一個類的類對象在內(nèi)存中是唯一的,這就說明我們通過這五種方式所創(chuàng)建的類方法是同一個對象旺隙。我們通過打印這5個類對象的內(nèi)存地址來驗證一下:
NSLog(@"%p \n %p \n %p \n %p \n %p \n", objectClass1, objectClass2, objectClass3, objectClass4, objectClass5);
打印結(jié)果:
0x10b0a6ea8
0x10b0a6ea8
0x10b0a6ea8
0x10b0a6ea8
0x10b0a6ea8
這也就驗證了這五個對象是同一個對象绒极,也就是一個類只有一個類對象。
既然每個類的類對象只有唯一一個蔬捷,那么在每個類的類對象中會存儲什么東西呢垄提?肯定是存放那么只需要存儲一份的東西,不會是像成員變量的值一樣,每個實例對象都可以有不同的成員變量值塔淤。
-
Class對象在內(nèi)存中存儲的信息主要包括:
isa指針
superclass指針
類的屬性信息(@property)摘昌,類的對象方法信息(instance method)
類的協(xié)議信息(@protocol)速妖,類的成員變量信息(ivars高蜂,類型,名稱等)
元類對象
- 元類對象的獲取方法
//我們在object_getClass()方法中傳入類對象就得到了元類對象罕容,每個類的元類對象只有一個备恤,所以objectMetaClass1和objectMetaClass2是同一個對象
Class objectMetaClass1 = object_getClass([NSObject class]);
Class objectMetaClass2 = object_getClass(objectClass1);
那么元類對象中存放的是什么信息呢?大家想一下實例對象和元類對象還有什么信息漏了就能明白元類信息中包含什么信息了锦秒。
-
meta-Class對象中包含的信息
isa指針
superclass指針
類的類方法信息
- class_isMetaClass()
class_isMetaClass()判斷傳進(jìn)去的對象是否是元類對象露泊。
BOOL result = class_isMetaClass(objectMetaClass1);
isa指針
我們先看一個Person類:
@interface Person:NSObject<NSCopying>
{
@public
int _age;
}
@property (nonatomic, assign)int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end
@implementation Person
- (void)personInstanceMethod{
}
+ (void)personClassMethod{
}
- (id)copyWithZone:(NSZone *)zone{
return nil;
}
這個Person類有成員變量,屬性旅择,有遵守的協(xié)議惭笑,實例方法,類方法生真。首先我們通過實例對象調(diào)用實例方法:
Person *person = [[Person alloc] init];
[person personClassMethod];
[Person personClassMethod]
這句代碼在底層的實現(xiàn)一定是objc_msgSend(person,@selector(personInstanceMethod))
沉噩。這里就有一個問題了,我們是給實例對象發(fā)消息柱蟀,調(diào)用實例方法川蒙,可以實例對象中沒有實例方法的信息呀。同樣的长已,當(dāng)我們調(diào)用類對象的時候[Person personInstanceMethod]
這句話在底層的實現(xiàn)時一定是轉(zhuǎn)化為objc_msgSend([Person class],@selector(personClassMethod))
這樣畜眨,也就是給一個類對象發(fā)送消息,調(diào)用類方法术瓮。但是類方法是在元類對象里面康聂,不在類對象中呀,這是怎么調(diào)用的呢胞四?這時isa指針就派上用場了恬汁。
Person實例對象的isa指針指向Person類對象,Person類對象的isa指針指向Person元類對象撬讽。
- 當(dāng)我們調(diào)用對象方法時蕊连,首先通過實例對象中的isa指針找到類對象,然后獲得類對象中的實例方法信息游昼。
- 當(dāng)我們調(diào)用類方法時甘苍,首先通過類對象的isa指針找到元類對象,再找到元類對象中的類方法信息烘豌,實現(xiàn)調(diào)用载庭。
superclass指針
類對象的superclass指針
我們創(chuàng)建一個子類Student類繼承自Person類。
@interface Student:Person <NSCoding>
{
@public
int _weight;
}
@property (nonatomic, assign)int height;
- (void)studentInstanceMethod;
+ (void)studentClassMethod;
@end
@implementation Student
- (void)studentInstanceMethod{
}
+ (void)studentClassMethod{
}
- (id)initWithCoder:(NSCoder *)aDecoder{
return nil;
}
- (void)encodeWithCoder:(NSCoder *)aCoder{
}
@end
我們看一下student實例對象調(diào)用實例方法:
Student *student = [[Student alloc] init];
[student studentInstanceMethod];
這個過程我們已經(jīng)很清楚了,就是通過student實例對象的isa指針來找到Student的類對象囚聚,然后在類對象中找到實例方法靖榕,完成調(diào)用。那么如果student實例對象調(diào)用的是父類Person類的實例方法呢顽铸?[student personInstanceMethod];
這個調(diào)用過程又是怎樣的呢茁计?- (void)personInstanceMethod
這個類方法肯定是存放在Person類的類方法里面的。這個時候就是superclass指針發(fā)揮作用的時候了谓松。
student實例對象首先通過其isa指針找到自己的類對象星压,然后Student類對象查找自己有沒有存儲- (void)personInstanceMethod
這個實例方法,發(fā)現(xiàn)自己并沒有這個實例方法的信息鬼譬,于是就通過自己的superclass指針來找到父類的類對象也就是Person類對象娜膘,Person類對象查看自己有沒有存儲- (void)personInstanceMethod
這個實例方法的信息,結(jié)果找到了這個實例方法的信息优质,至此student實例對象(^-^)也就獲取了- (void)personInstanceMethod
實例方法的信息竣贪。
類對象的superclass指針指向父類的類對象
元類對象的superclass指針
首先我們來看Student類對象調(diào)用自己的類方法:
[Student studentClassMethod];
這個調(diào)用過程應(yīng)該已經(jīng)很清楚了,Student類的類方法信息是存儲在元類對象中的巩螃。Student類對象首先通過自己的isa指針找到Student元類對象演怎,Student元類對象查看自己有沒有studentClassMethod
這個類方法的信息,查看后發(fā)現(xiàn)有牺六,就傳給類對象颤枪。那么如果要調(diào)用父類Person類的類方法呢?
[Student personClassMethod];
首先Student類對象通過自己的isa指針找到Student元類對象淑际,Student元類對象查看自己有沒有personClassMethod
這個類方法的信息畏纲,查找后沒有就利用自己的superclass指針找到父類的元類對象,也就是Person類的元類對象春缕,Person類的元類對象查看后發(fā)現(xiàn)自己有personClassMethod
這個類方法的信息盗胀,至此Student類對象就找到了personClassMethod
這個類方法的信息。
元類對象的superclass指針指向父類的元類對象锄贼。
isa,superclass總結(jié)
放上一張經(jīng)典的總結(jié)圖
總結(jié)起來就是:
實例對象的isa指針指向該類的類對象票灰。
類對象的isa指針該類的元類對象。
元類對象的isa指針指向基類的元類對象宅荤。
類對象的superclass指針指向父類的類對象
元類對象的superclass指向父類的元類對象(基類除外)
基類的元類對象的superclass指向基類的類對象屑迂。
isa指針的一個小細(xì)節(jié)
前面已經(jīng)說了實例對象的isa指針指向類對象,類對象的isa指向元類對象惹盼。所以實例對象的isa指針的值就是類對象的地址值,類對象的isa指針的值就是元類對象的地址值惫确。我們打印看一下結(jié)果:
Person *person = [[Person alloc] init];
Class personClass = [Person class];
Class personMetaClass = object_getClass(personClass);
NSLog(@"%p, %p, %p", person, personClass, personMetaClass);
打印結(jié)果:
Student[1372:63404] 0x600000008390, 0x10e7a4130, 0x10e7a4108
然后我們再打個斷點(diǎn)蚯舱,查看一下各個對象的isa指針值兄裂。
我們在調(diào)試框中輸入
p person->isa
發(fā)現(xiàn)打印的是$0 = Person
蜈亩,這并不是我們想要的稚配,我們輸入p (long)person->is
,這下打印出想要的結(jié)果了$1 = 4537860400
港华,但是這是10進(jìn)制表示道川,我們需要轉(zhuǎn)化為16進(jìn)制,再輸入p/x (long)person->isa
立宜,打印得到16進(jìn)制結(jié)果:$2 = 0x000000010e7a4130
冒萄。這也就驗證了實例對象的isa指針指向類對象。然后我們試著打印類對象的isa指針的值:
p/x personClass->isa
打印結(jié)果是:
error: member reference base type 'Class' is not a structure or union
所以這條路行不通了橙数。我們已經(jīng)知道了這個personClass這個類對象中第一個成員變量一定是isa指針尊流,所以我們可以自己創(chuàng)建一個結(jié)構(gòu)體:
struct pd_objc_class{
Class isa
};
然后我們把personClass這個類對象強(qiáng)制轉(zhuǎn)化成pd_objc_class結(jié)構(gòu)體類型:
struct pd_objc_class *personClass2 = (__bridge struct pd_objc_class *)(personClass);
然后再通過p/x personClass2->isa
打印isa指針的值:
$0 = 0x000000010583f108
,同時我們從一開始的打印結(jié)果可以找到personMetaClass對象的地址為0x000000010583f108灯帮。
這樣也就證明了類對象的isa指針是指向元類對象的崖技。
驗證實例對象,類對象钟哥,元類對象的結(jié)構(gòu)
類對象和元類對象的類型都是Class類型迎献,所以本質(zhì)上來講它們的結(jié)構(gòu)是一致的。那么我們只需要搞清楚這個Class類型的結(jié)構(gòu)就可以搞清楚類對象和元類對象的結(jié)構(gòu)了腻贰。
按住command點(diǎn)擊Class查看結(jié)構(gòu):
typedef struct objc_class *Class;
可以看到這是一個obkc_class類型的結(jié)構(gòu)體指針吁恍。然后我們繼續(xù)點(diǎn)擊進(jìn)objc_class查看:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
但是我們看到這個是條件編譯,#if !OBJC2也就是如果不是OBJC2就編譯播演,但是現(xiàn)在就是OBJC2冀瓦,所以這段條件代碼不會編譯。因此這個代碼就不足以作為參考写烤。那么我們只好從源碼中查看objc_class的結(jié)構(gòu)翼闽。
我們在源碼中搜索objc_class
,從objc-runtime-new.h
中找到了objc_class的結(jié)構(gòu):
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
}
可以看到顶霞,objc_class這個結(jié)構(gòu)體是繼承自objc_object肄程,我們點(diǎn)進(jìn)objc_object結(jié)構(gòu)體中看看锣吼,可以看到
struct objc_object {
private:
isa_t isa;
}
里面只有一個成員變量isa指針。所以objc_class這個結(jié)構(gòu)體的結(jié)構(gòu)就等價于:
struct objc_class : objc_object {
void *isa;;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
}
第一個成員變量是isa指針蓝厌,第二個是superclass指針玄叠。第三個cache是和方法的緩存有關(guān)的。第四個bits先不管拓提。第五個是一個方法读恃,返回值是class_rw_t類型的,我們點(diǎn)進(jìn)class_rw_t看看它的結(jié)構(gòu):
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;//方法列表
property_array_t properties;//屬性列表
protocol_array_t protocols;//協(xié)議列表
}
然后我們再點(diǎn)進(jìn)class_ro_t看看它的結(jié)構(gòu):
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;//類名
method_list_t * baseMethodList;//方法列表
protocol_list_t * baseProtocols;//協(xié)議列表
const ivar_list_t * ivars;//成員變量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;//屬性列表
method_list_t *baseMethods() const {
return baseMethodList;
}
};
從這個角度確實可以證明objc_class的結(jié)構(gòu)中有isa指針代态,superclass指針寺惫,方法列表,屬性列表蹦疑,成員變量列表西雀,協(xié)議列表等。
通過轉(zhuǎn)化為C++的源碼來證實實例對象歉摧,類對象艇肴,元類對象的結(jié)構(gòu)
類對象
@interface Person:NSObject<NSCopying>
{
@public
int _age;
}
@property (nonatomic, assign)int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end
@implementation Person
- (void)personInstanceMethod{
}
+ (void)personClassMethod{
}
- (id)copyWithZone:(NSZone *)zone{
return nil;
}
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person->_age = 10;
person.no = 3;
Class personClass = [Person class];
Class personMetaClass = object_getClass(personClass);
return 0;
}
}
我們把person類的代碼轉(zhuǎn)為C++的源碼:
我們找到class_t類型的OBJC_CLASS$_Person這個結(jié)構(gòu)體,這個結(jié)構(gòu)體就死類對象的結(jié)構(gòu)叁温。
struct _class_t OBJC_CLASS_$_Person __attribute__ ((used, section ("__DATA,__objc_data"))) = {
0, // &OBJC_METACLASS_$_Person,
0, // &OBJC_CLASS_$_NSObject,
0, // (void *)&_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_CLASS_RO_$_Person,
我們找到_class_t這個結(jié)構(gòu)體,查看結(jié)構(gòu)
struct _class_t {
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};
我們看到其中第一個成員是isa指針再悼,第二個成員是superclass指針,cache和vtable我們先不管膝但。接著我們看到_class_ro_t這個結(jié)構(gòu)體:
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;
const struct _ivar_list_t *ivars;
const unsigned char *weakIvarLayout;
const struct _prop_list_t *properties;
};
這個結(jié)構(gòu)體我們前面見多過冲九,應(yīng)該已經(jīng)比較熟悉了。然后我們找到類對象中這個結(jié)構(gòu)的實現(xiàn):
static struct _class_ro_t _OBJC_CLASS_RO_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
0,
__OFFSETOFIVAR__(struct Person, _age),
sizeof(struct Person_IMPL),
(unsigned int)0,
0,
"Person",
(const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_Person,
(const struct _objc_protocol_list *)&_OBJC_CLASS_PROTOCOLS_$_Person,
(const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_Person,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person,
};
- 我們可以看到instanceSize對應(yīng)的是sizeof(struct Person_IMPL)跟束。
- name也就是類名莺奸,對應(yīng)的是"Person"。
- baseMethods對應(yīng)的是OBJC$_INSTANCE_METHODS_Person這個結(jié)構(gòu)體泳炉,我們找到這個結(jié)構(gòu)體的實現(xiàn):
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[4];
} _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
4,
{{(struct objc_selector *)"personInstanceMethod", "v16@0:8", (void *)_I_Person_personInstanceMethod},
{(struct objc_selector *)"copyWithZone:", "@24@0:8^{_NSZone=}16", (void *)_I_Person_copyWithZone_},
{(struct objc_selector *)"no", "i16@0:8", (void *)_I_Person_no},
{(struct objc_selector *)"setNo:", "v20@0:8i16", (void *)_I_Person_setNo_}}
};
我們通過INSTANCE_METHODS這個名字知道這個里面存放的是實例方法憾筏,通過其初始化可以看到,有四個實例方法花鹅,方法名分別是"personInstanceMethod";"copyWithZone:";"no";"setNo:"氧腰。這是符合實際的。
- baseProtocols根據(jù)名字應(yīng)該是存放的協(xié)議信息刨肃,它是用OBJC_CLASS_PROTOCOLS$_Person這個結(jié)構(gòu)體初始化的古拴。我們找到這個結(jié)構(gòu)體的實現(xiàn):
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CLASS_PROTOCOLS_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSCopying
};
可以看到,協(xié)議數(shù)量是1真友,協(xié)議是_OBJC_PROTOCOL_NSCopying黄痪,通過名稱我們得知是NSCopying,這里不再展開盔然。
- ivars是變量的意思桅打,其對應(yīng)的是OBJC$_INSTANCE_VARIABLES_Person這個結(jié)構(gòu)體是嗜,我們查看一下這個結(jié)構(gòu)體的結(jié)構(gòu):
static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[2];
} _OBJC_$_INSTANCE_VARIABLES_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
2,
{{(unsigned long int *)&OBJC_IVAR_$_Person$_age, "_age", "i", 2, 4},
{(unsigned long int *)&OBJC_IVAR_$_Person$_no, "_no", "i", 2, 4}}
};
可以看到它有兩個成員變量,其中一個是_age,類型是int挺尾,大小是4字節(jié)鹅搪,還有一個成員變量是_no,類型是int遭铺,大小是4字節(jié)丽柿。
- properties是屬性,可以猜測里面存儲的是屬性信息魂挂。我們看到其對應(yīng)的結(jié)構(gòu)體是_PROP_LIST_Person甫题,查看一下其實現(xiàn):
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"no","Ti,N,V_no"}}
};
我們可以看到有一個屬性,屬性名是no涂召。
元類對象
我們找到class_t類型的結(jié)構(gòu)體OBJC_METACLASS$_Person坠非,這個結(jié)構(gòu)體就是元類對象的實現(xiàn):
struct _class_t OBJC_METACLASS_$_Person __attribute__ ((used, section ("__DATA,__objc_data"))) = {
0, // &OBJC_METACLASS_$_NSObject,
0, // &OBJC_METACLASS_$_NSObject,
0, // (void *)&_objc_empty_cache,
0, // unused, was (void *)&_objc_empty_vtable,
&_OBJC_METACLASS_RO_$_Person,
我們找到OBJC_METACLASS_RO$_Person這個結(jié)構(gòu)體
static struct _class_ro_t _OBJC_METACLASS_RO_$_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
sizeof(struct _class_t),
sizeof(struct _class_t),
(unsigned int)0,
0,
"Person",
(const struct _method_list_t *)&_OBJC_$_CLASS_METHODS_Person,
0,
0,
0,
0,
};
我們通過這個名稱可以看出這是為初始化元類對象的。
還是貼一下_class_ro_t的結(jié)構(gòu):
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;
const struct _ivar_list_t *ivars;
const unsigned char *weakIvarLayout;
const struct _prop_list_t *properties;
};
對比來看芹扭,OBJC_METACLASS_RO_$_Person這個結(jié)構(gòu)體的初始化就要簡單很多麻顶。
- name就是類名,是"Person"舱卡。
- baseMethods存放的是方法,其是用OBJC$_CLASS_METHODS_Person這個結(jié)構(gòu)體初始化的队萤,我們查看一下這個結(jié)構(gòu)體的結(jié)構(gòu):
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CLASS_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"personClassMethod", "v16@0:8", (void *)_C_Person_personClassMethod}}
};
類方法只有一個轮锥,方法名是“personClassMethod”。
其他的如baseProtocols,ivars,properties這些都是空的要尔。
這也就證實了元類對象中只有isa指針舍杜,superclass指針,還有類方法赵辕。