在OC中碧绞,對(duì)象分為三種
- instance對(duì)象(實(shí)例對(duì)象)
- class對(duì)象(類對(duì)象)
- meta-class對(duì)象(元類對(duì)象)
instance對(duì)象
instance對(duì)象就是類通過alloc出來的對(duì)象府框,每次alloc都會(huì)產(chǎn)生一個(gè)新的 instance對(duì)象。
NSObject *obj1 = [[NSObject alloc]init];
NSObject *obj2 = [[NSObject alloc]init];
在上面的代碼中讥邻,obj1 和 obj2就是兩個(gè)不同的實(shí)例對(duì)象寓免。
instance對(duì)象中存儲(chǔ)著isa和其他的成員變量
@interface ZJPerson : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int height;
@property (nonatomic, assign) int weight;
@end
ZJPerson *person = [[ZJPerson alloc]init];
person.age = 19;
person.height = 179;
person.weight = 69;
在上面的例子中,
person這個(gè)實(shí)例對(duì)象就存儲(chǔ)著 isa 以及 _age, _height, _weight四個(gè)變量
類對(duì)象
每個(gè)類有且只有一個(gè)類對(duì)象
通過
+ (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead");
或者通過runtime函數(shù)
object_getClass(id _Nullable obj) ;
來獲取
NSLog(@"%p --- %p", [ZJPerson class], object_getClass(person));
類對(duì)象里存儲(chǔ)著
- isa 指針
- superClass 指針
- 屬性 信息
- 實(shí)例方法 信息
- 協(xié)議 信息
- 成員變量 信息
此處的成員變量和實(shí)例對(duì)象中的成員變量不太一樣计维,實(shí)例對(duì)象中的成員變量記錄的是值,而類對(duì)象里的成員變量記錄的是成員變量的類型以及名字撕予。
元類對(duì)象
每個(gè)類有且只有一個(gè)元類對(duì)象
通過runtime函數(shù)
object_getClass(id _Nullable obj) ;
來獲取鲫惶,只不過參數(shù)傳類對(duì)象
//實(shí)例對(duì)象
ZJPerson *person = [[ZJPerson alloc]init];
//類對(duì)象
Class class = object_getClass(person);
//元類對(duì)象
Class metaClass = object_getClass(class);
元類對(duì)象里存儲(chǔ)著
- isa 指針
- superClass 指針
- 類方法信息
那么這三者之間有什么聯(lián)系呢?
isa指針
我們繼續(xù)在ZJPerson類添加一個(gè)實(shí)例方法和一個(gè)類方法
@interface ZJPerson : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int height;
@property (nonatomic, assign) int weight;
- (void)personInstanceTest;
+ (void)personClassTest;
@end
然后調(diào)用它們
ZJPerson *person = [[ZJPerson alloc]init];
[person personInstanceTest];
[ZJPerson personClassTest];
上面這調(diào)用方法的代碼轉(zhuǎn)換成C\C++偽代碼如下:
objc_msgSend(person, @selector(personInstanceTest));
objc_msgSend([ZJPerson class], @selector(personClassTest));
第一句我們可以看到給實(shí)例對(duì)象發(fā)送了消息实抡,調(diào)用了存儲(chǔ)在類對(duì)象里的方法
第二句可以看到給類對(duì)象發(fā)送了消息欠母,調(diào)用了存儲(chǔ)在元類對(duì)象里的方法
那么這又是怎么實(shí)現(xiàn)的呢
instance對(duì)象的isa指針指向class對(duì)象
當(dāng)調(diào)用personInstanceTest方法的時(shí)候欢策,instance對(duì)象通過isa指針找到class對(duì)象,然后再?gòu)腸lass對(duì)象里存儲(chǔ)的方法列表里找到方法來調(diào)用
class對(duì)象的isa指針指向meta-class對(duì)象
當(dāng)調(diào)用personClassTest方法的時(shí)候赏淌,class對(duì)象通過isa指針找到meta-class對(duì)象踩寇,然后再?gòu)膍eta-class對(duì)象里存儲(chǔ)的方法列表里找到方法來調(diào)用
superClass指針
我們?cè)傩陆硪粋€(gè)類ZJStudent,繼承自ZJPerson
@interface ZJStudent : ZJPerson
- (void)studentInstanceTest;
+ (void)studentClassTest;
@end
然后初始化ZJStudent對(duì)象六水,調(diào)用ZJPerson的personInstanceTest方法和personClassTest方法
ZJStudent *student = [[ZJStudent alloc]init];
[student personInstanceTest];
[ZJStudent personClassTest];
那么上面這段代碼底層邏輯又該怎么走呢
當(dāng)調(diào)用personInstanceTest方法的時(shí)候俺孙,ZJStudent的instance對(duì)象通過isa指針找到ZJStudent的class對(duì)象,然后在ZJStudent的class對(duì)象存儲(chǔ)的方法列表中找personInstanceTest掷贾,發(fā)現(xiàn)沒有睛榄,就繼續(xù)通過superClass指針找到ZJPerson的class對(duì)象,然后再ZJPerson的class對(duì)象存儲(chǔ)中的方法列表里找到并調(diào)用
同理想帅,調(diào)用personClassTest方法的時(shí)候场靴,ZJStudent的class對(duì)象通過isa指針找到ZJStudent的meta-class對(duì)象,然后在ZJStudent的meta-class對(duì)象存儲(chǔ)的方法列表中找personClassTest港准,發(fā)現(xiàn)沒有旨剥,就繼續(xù)通過superClass指針找到ZJPerson的meta-class對(duì)象扒俯,然后再ZJPerson的meta-class對(duì)象存儲(chǔ)中的方法列表里找到并調(diào)用
總結(jié)
- instance的isa指向class
- class的isa指向meta-class
- meta-class的isa指向基類的meta-class
- class的superclass指向父類的class
- 如果沒有父類毕骡,則superclass為nil
- meta-class的superclass指向父類的meta-class
- 基類的meta-class的superclass指向基類的class
- instance方法調(diào)用軌跡
- 通過instance的isa指針找到class愁憔,如果不存在就通過superclass指針往上找衬潦,直到為nil弛槐,拋出異常
- class方法調(diào)用軌跡
- 通過class的isa指針找到meta-class关噪,如果不存在就通過superclass指針往上找衔憨,直到為nil严卖,拋出異常
補(bǔ)充
基類的meta-class對(duì)象的superclass指針指向基類的class對(duì)象
我們通過代碼來證明
首先創(chuàng)建一個(gè)類繼承自NSObject
@interface ZJPerson : NSObject
+ (void)test;
@end
@implementation ZJPerson
+ (void)test {
NSLog(@"+[ZJPerson test] ---- %p", self);
}
@end
然后再創(chuàng)建一個(gè)NSObject的分類烟具,實(shí)現(xiàn)相同的類方法
@interface NSObject (test)
+ (void)test;
@end
@implementation NSObject (test)
+ (void)test {
NSLog(@"+[NSObject test] ---- %p", self);
}
@end
最后再命令行項(xiàng)目的main函數(shù)中
NSLog(@"[ZJPerson class] --- %p", [ZJPerson class]);
NSLog(@"[NSObject class] --- %p", [NSObject class]);
[ZJPerson test];
[NSObject test];
輸出如下
可以看到
ZJPerson調(diào)用ZJPerson自己的test方法梢什,NSObject調(diào)用NSObject自己的test方法,沒啥問題
接下來朝聋,我們把ZJPerson的test方法注釋掉嗡午,再此運(yùn)行一次代碼看看輸出情況
@interface ZJPerson : NSObject
+ (void)test;
@end
@implementation ZJPerson
//+ (void)test {
// NSLog(@"+[ZJPerson test] ---- %p", self);
//}
@end
可以看到[ZJPerson test]方法調(diào)用的是NSObject的test方法,這根據(jù)前面的調(diào)用邏輯來推理冀痕,調(diào)用test方法荔睹,給ZJPerson的class對(duì)象發(fā)送了消息,根據(jù)isa指針找到ZJPerson的meta-class言蛇,發(fā)現(xiàn)沒有此方法僻他,再根據(jù)superclass指針往上找到NSObject的meta-class,發(fā)現(xiàn)此方法腊尚,調(diào)用吨拗,也啥大問題。
再接下來我們把NSObject的+test方法注釋掉,新增一個(gè)-test方法
@implementation NSObject (test)
//+ (void)test {
// NSLog(@"+[NSObject test] ---- %p", self);
//}
- (void)test {
NSLog(@"-[NSObject test] ---- %p", self);
}
@end
再運(yùn)行一次再看輸出結(jié)果
可以看到我調(diào)用[ZJPerson test]方法劝篷,實(shí)際上調(diào)用的卻是NSObject的-test方法哨鸭,這是為什么呢?
我們可以梳理下邏輯
首先調(diào)用[ZJPerson test]娇妓,給ZJPerson的class對(duì)象發(fā)送調(diào)用test方法消息像鸡,然后class對(duì)象通過isa指針找到ZJPerson的meta-class對(duì)象,發(fā)現(xiàn)方法列表里沒有test方法哈恰,就通過superclass指針繼續(xù)往上找只估,找到NSObject的meta-class對(duì)象,發(fā)現(xiàn)它的方法列表里也沒有蕊蝗,就繼續(xù)通過superclass往上找仅乓,找到了NSObject的class對(duì)象,剛好有test方法蓬戚,調(diào)用
再補(bǔ)充
instance對(duì)象里的isa存儲(chǔ)的并不是class對(duì)象的地址值夸楣,而是isa指針&ISA_MASK才是class對(duì)象的值
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
#endif
因?yàn)槭莔ac的命令行項(xiàng)目,所以&的是x86_64下ISA_MASK的值
面試題
- 對(duì)象的isa指針指向哪里子漩?
- instance對(duì)象的isa指向class對(duì)象
- class對(duì)象的isa指向meta-class對(duì)象
- meta-class對(duì)象的isa指向基類的meta-class對(duì)象
- OC的類信息存放在哪里豫喧?
- 成員變量具體的值,存放在instance對(duì)象中
- 成員變量幢泼、協(xié)議紧显、實(shí)例方法、屬性缕棵,存放在class對(duì)象中
- 類方法孵班,存放在meta-class對(duì)象中