先看一道题:
#import "RootVC.h"
@interface NSObject (myTest)
+ (void) testFunc;
@end
@implementation NSObject (myTest)
-(void) testFunc
{
NSLog(@"testFunc: 执行");
}
@end
@implementation RootVC
- (void)viewDidLoad
{
[super viewDidLoad];
[NSObject testFunc];
NSObject *obj = [[NSObject alloc]init];
[obj testFunc];
}
@end
程序执行的结果是什么?为什么呢?
如果要解答这道题,就需要对OC的方法调用过程非常了解才能答对。
先看看运行结果:
2017-08-07 15:38:14.835648+0800 testruntime[12184:1830852] testFunc: 执行
2017-08-07 15:38:14.835712+0800 testruntime[12184:1830852] testFunc: 执行
类在Runtime中的结构
struct objc_class
{
Class isa OBJC_ISA_AVAILABILITY;
//isa指针,这是个啥?,表示是一个什么,
//实例的isa指向类对象,类对象的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;
/* Use `Class` instead of `struct objc_class *` */
元类MetaClass
所有的类自身也是一个对象,可以向这个对象发送消息(即调用类方法)。
NSArray *array = [NSArray array];
+array消息发送给了NSArray类,而这个NSArray也是一个对象。
既然是对象,那么它也是一个objc_object指针,包含一个指向其类的一个isa指针。为了调用+array方法,这个类的isa指针必须指向一个包含这些类方法的一个objc_class结构体。这就需要meta_class概念。
meta_class是一个类对象的类。当我们向一个对象发送消息时,Runtime会在这个对象所属的这个类的方法列表中查找方法。而向一个类发送消息时,会在这个类的meta_class的方法列表中查找。meta_class很重要,因为它存储着一个类的所有的类方法。每个类都会有一个单独的meta_class,因为每个类的类方法基本不可能完全相同。
方法调用的过程
调用方法分为调用实例方法和调用类方法,在结构体我们可以看到第一个就是一个 isa 指针,会指向类对象或者元类。
每个实例对象有个isa的指针,指向对象的类;
类里也有个isa的指针, 指向meteClass(元类)。
元类保存了类方法的列表。当类方法被调用时,先会从元类本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass)。根元类的isa指针指向本身,这样形成了一个封闭的内循环。
方法调用的过程
1.在对象自己缓存的方法列表中去找要调用的方法,找到了就直接执行其实现。
2.缓存里没找到,就去上面说的它的方法列表里找,找到了就执行其实现。
3.还没找到,说明这个类自己没有了,就会去向其父类里执行1、2。
4.如果找到了根类还没找到,那么就是没有了,会转向一个拦截调用的方法,我们可以自己在拦截调用方法里面做一些处理。
5.如果没有在拦截调用里做处理,那么就会报错崩溃。
以上就是方法调用的过程。我们可以看到的是,所谓重写父类方法,并不是抹除了父类方法,父类的方法还是存在的,只是我们在子类里面找到了就不会再去父类里找了,是这个层面的“覆盖”。而我们熟悉的 super 调用父类的方法实现,就是告知要去调用父类实现的标识。
这样回到开头的题目,答案自然一目了然啊。
类方法去metaclass里面找,没找到,去superclass也就是NSObject去找,找到了方法就去执行。
实例方法直接找到就进行执行。