假设A类继承自B类,B类继承自NSObject
A便是途中的Subclass(class),B便是图中的Superclass(class),NSObject便是Root class(class);
A *a = [A new];
其实A和a一样,也是对象,A称为类对象,a称为实例对象。
每一个类对象都有一个isa指针,指向元类。
我们新建一个NSObject的分类NSObject+Method
我们在里面实现两个方法
- (void)sayObject{
NSLog(@"NSObject===%s",__func__);
}
+ (void)sayClass{
NSLog(@"NSObject===%s",__func__);
}
新建一个AClass
类继承于NSObject,我们直接调用如下代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
AClass *p = [AClass alloc];
[AClass performSelector:@selector(sayClass)];
[AClass performSelector:@selector(sayObject)];
NSLog(@"Hello, World!");
}
return 0;
}
2021-07-01 11:31:23.567714+0800 KCObjcBuild[70678:3948791] NSObject===+[NSObject(Method) sayClass]
2021-07-01 11:31:23.568099+0800 KCObjcBuild[70678:3948791] NSObject===-[NSObject(Method) sayObject]
可以看出来两个都调用成功了,在oc底层其实没有“+”
和“-”
方法的区分,只是方法存储的位置不同而已。我们具体分析下调用成功的流程:
1、NSObject中sayClass
是类方法,在NSObject的元类
(Root Class Meta)中存储,AClass调用sayClass
方法,会从AClass的元类中找,找不到的话。从元类的父类找(Super Class Meta),继续找根元类(Root Class Meta),此时找到imp
,返回。
2、NSObject中sayObject
的实例方法,在NSObject
的类中存储,AClass调用sayObject
,会从AClass的元类中找,找不到的话。从元类的父类找(Super Class Meta),继续找根元类(Root Class Meta),再继续找(Root Class class)找到NSObject,此时找到imp
,返回。
我们再进一步,用实例对象p调用看看是怎样的结果
int main(int argc, const char * argv[]) {
@autoreleasepool {
AClass *p = [AClass alloc];
//[p performSelector:@selector(sayClass)];
[p performSelector:@selector(sayObject)];
NSLog(@"Hello, World!");
}
return 0;
}
我们发现sayObject调用成功了,sayClass直接崩溃,找不到方法
具体分析一下调用流程:
3、sayObject
调用成功,我们很好理解。直接AClass
继承于NSObject,AClass里面没有实现,直接找到父类的实现。
4、sayClass
没有调用成功,我们前面分析过sayClass存在NSObject的元类
(Root Class Meta)当中,对象去调用方法的时候,先查找AClass
类中查找,此时没有,再往父类(NSObject)中查找,我们发现此时也没有。再找NSObject的父类就是nil了,所以查找不到。因为这个流程并没有查找到NSObject的元类
(Root Class Meta)当中去,所以找不到。
总结实例对象可以调用NSObject里面的所有实例方法,但不能调用类方法,类可以掉用NSObject里面的所有方法,包括类方法和实例方法。
原因是类调用方法时,是从类的元类的方法列表里查找,一直往上找到元类的父类,一直到根元类,根元类的isa指向自己,NSObject的isa也指向它,从而找到NSObject的类方法列表,储存在根元类的方法列表中。
根元类的父类指向NSObject,因而找到NSObject方法列表。NSObject的方法列表中存储了NSObject的实例方法。
实例对象调用方法是先在自己的方法列表里找,然后父类,然后父类的父类,直到NSObject。NSObject的方法列表中存储了NSObject的实例方法。从而可以调用NSObject的实例方法。
NSObject的父类指向nil。所以无法找到NSObject的类方法列表,无法调用类方法。
有点绕,但只要看懂了上面的图形指向,就一目了然了。