isa指针
OC是一门面向对象的语言,每一个对象都是类的一个实例。在objective-c语言的内部,每一个对象都有一个isa指针,指向该指针的类。每一个类描述了一系例他的实例的特点,包括成员变量的列表,成员函数的列表。每一个对象都可以接收消息,而对象接收消息列表保存在他所对应的类中。
当我们初始化一个对象的时候,是怎么发送消息的
NSObject *obj=[[NSObject alloc] init];
调用方法,其实是给对象发送消息,在编译时这句话会翻译成一个C的函数调用,即:
objc_msgSend(objc_msgSend([NSObject class],@selector(alloc)),@selector(init));
使用这个函数的需要引入头文件:
#import <objc/message.h>
那不是把 OC代码转换成C。C语言函数在调用编译的时候就会决定调用哪个函数,而OC是一种动态语言,他会尽可能把代码的从编译链接是推迟到运行时,这就是OC运行时多态。 (给一个对象发送消息,并不会立即执行,而是在运行的时候在去寻找他对应的实现)
在Xcode中打开,NSObject.h和objc.h,我们可以看到,NSObject就是一个包含isa指针的结构体,按照面向对象的设计原则,所有的事物都应该是对象,所以严格的说OC并不是完全面向对象的(因为含有int double 类型的变量)。在OC语言中,每一个类实际上也是一个对象。每一个类也有一个isa指针。每一个类也可以接收消息,例如代码[NSObject alloc],就是向NSObject这个类发送名为 “alloc” 的消息。
在oc中,因为类也是一个对象,所以也必须是另外一个类的实例,这个类就是元类(metaclass)。
- 类对象保存了对象方法的列表。当一个对象方法被调用的时候,对象首先通过isa指针查找到对应的类,类对象会首先查找他本身是否有该方法的实现,如果没有,则元类对象会向他的父类查找方法,这样就可以一直找到继承链的头
- 元类对象保存了类方法的列表。当一个类方法被调用的时候,类对象首先通过isa指针查找到对应的元类对象,元类对象会首先查找他本身是否有该方法的实现,如果没有,则元类对象会向他的父类查找方法,这样就可以一直找到继承链的头
一个 Objective-C 方法会被编译成 objc_msgSend,这个函数有两个默认参数,id 类型的 self, SEL 类型的 op。我们先看看 id 的定义:
typedef struct objc_object *id;
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
我们可以看到,在 objc_object 结构体中,只有一个指向 Class 类型的 isa 指针。
我们再看看 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;
里面有很多参数,很显眼的能看到这一行:
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
看名字也容易理解,这个 methodLists 就是用来存放方法列表的。我们再看看 objc_method_list 这个结构体:
struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
里面的 objc_method ,也就是我们熟悉的 Method:
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
Method 里面保存了三个参数:
- 方法的名称
- 方法的类型
- 方法的具体实现,由 IMP 指针指向
经过层层挖掘,我们能明白实例对象调用方法的大致逻辑:
MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];
- 先被编译成 ((void (*)(id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));
- 沿着入参 myClass 的 isa 指针,找到 myClass 的类对象(Class),也就是 MyClass
- 接着在 MyClass 的方法列表 methodLists 中,找到对应的 Method
- 最后找到 Method 中的 IMP 指针,执行具体实现
类对象的类方法又是怎么找到并执行的?
由上文,我们已经知道,实例对象是通过 isa 指针,找到其类对象(Class)中保存的方法列表中的具体实现的。
比如:
MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];
可以理解为:printLog 方法就是保存在 MyClass 中的。
那么如果是个类方法,又是保存在什么地方的呢?
我们回顾下 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;
可以发现到这一行:
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
这里的 isa 同样是指向一个 Class 的指针。上文中,我们也知道了类对象的 isa 指针是指向元类对象的。那么不难得出:
类对象的类方法,是保存在元类对象中的!
类对象和元类对象都是 Class 类型,仅仅服务的对象不同罢了。找到了元类对象,自然就找到了元类对象中的 methodLists,接下来就和实例对象的方法寻找调用一样的流程了。
关于对象、类、元类关系:
对象、类、元类关系
对象、类、元类关系
从上图可以看出:
对象的isa指针指向类对象,类对象的isa指针指向元类,元类的isa指针指向根元类,根元类的isa指针指向自身。
NSObject的元类的父类是NSObject , NSObject的isa指针又指向NSObject的元类,所以:
- NSObject类对象 里面的所有的实例对象方法,NSObject元类 也都拥有用(类对象里面的是实例对象的方法,元类对象里面的是类对象的方法)
- NSObject类对象 可以调用任意 NSObject对象 里的实例方法(因此,所有继承自NSObject的类对象,都可以调用NSObject的实例方法)
注意:在Objective-C中,几乎所有的类都是继承与NSObject,这里的几乎所有是因为官网关于Cocoa框架有介绍:Cocoa supplies two root classes: NSObject and NSProxy.不继承NSObject的都继承NSProxy,因为NSProxy的应用比较特殊,在Cocoa程序中比较少见,具体的例子可以参考官方API说明文档中关于NSProxy类的介绍,里面讲到了一些例子,比如NSDistantObject
定义Person类
image.png
使用object_getClass()可以获取某个对象的isa指针指向的对象:
Person *p = [[Person alloc] init];
id class = object_getClass(p1);
id metaClass = object_getClass(class);
id rootMetaClass = object_getClass(metaClass);
[self printMethods:class];
[self printMethods:metaClass];
[self printMethods:rootMetaClass];
//下面方法可以打印出类对象的名称和里面的方法
- (void) printMethods:(Class)cls
{
unsigned int count ;
Method *methods = class_copyMethodList(cls, &count);
NSMutableString *methodNames = [NSMutableString string];
[methodNames appendFormat:@"%@ - ", cls];
for (int i = 0 ; i < count; i++) {
Method method = methods[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
[methodNames appendString: methodName];
[methodNames appendString:@" "];
}
NSLog(@"%@",methodNames);
free(methods);
}
打印结果:
打印结果
对象的isa指针指向它的类对象,类对象的isa指针指向它的元类对象,元类的isa指针指向它的根元类对象(这里是NSObject)
从打印结果可以看出,类对象打印出来的是对象的方法,元类对象打印出来的是类方法。
链接:https://blog.csdn.net/qq_22854687/article/details/51245568