iOS - isa、superclass指针,元类supercl
一. 对象
instance对象,也称实例对象
class对象,也称类对象
meta-calss对象,也称元类对象
本文将探讨如下问题:
- 这些对象中包含了什么信息
- 这些信息如何关联
- 成员变量是存在哪里的
- 类方法与实例方法是存在哪里的
- 如何找到superclass
instance对象
1. 什么是instance对象
通过+alloc
之后的都是instance
对象.这里还需要强调的是:instance
对象不仅仅是NSObject对象,还有一个代理类NSProxy,也能创建instance
对象
// 创建一个对象
NSObject* obj = [NSObject alloc];
Class对象
1. 获取class对象
创建一个继承自NSObject
的ClassObject
类
- (void)viewDidLoad {
[super viewDidLoad];
ClassObject *clsObj = [[ClassObject alloc] init];
[self fetchClassWithClassObject:clsObj];
}
//获取class的所有方法
- (void)fetchClassWithClassObject:(ClassObject *)clsObj {
Class objClass1 = [clsObj class];
Class objClass2 = [ClassObject class];
Class objClass3 = object_getClass(clsObj);
NSLog(@"\n objClass1 = %@ \n objClass2 = %@ \n objClass3 = %@ \n",NSStringFromClass(objClass1), NSStringFromClass(objClass2), NSStringFromClass(objClass3) );
NSLog(@"\n\n objClass1 = %p \n objClass2 = %p \n objClass3 = %p", objClass1, objClass2, objClass3);
}
//打印结果
2021-05-04 10:34:36.333929+0800 ClassDemo[26079:8911440]
objClass1 = ClassObject
objClass2 = ClassObject
objClass3 = ClassObject
2021-05-04 10:34:36.334052+0800 ClassDemo[26079:8911440]
objClass1 = 0x109a61548
objClass2 = 0x109a61548
objClass3 = 0x109a61548
打印可见,不管用什么方式获取的class对象都是一样的,打印地址也一致,说明在项目中一个Class仅有一个对象,但是上面三种获取class的方式有什么不同呢?
前两种是通过方法获取的,直接获取的是当前instance的Class, 但是第三种方式不一样,这种方式获取的是当前instance的isa的值
可以这样做个实验:给clsObj
添加一个kvo
[clsObj addObserver:self forKeyPath:@"class" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
打印结果变为
2021-05-04 10:54:32.415419+0800 ClassDemo[28990:8934226]
objClass1 = ClassObject
objClass2 = ClassObject
objClass3 = NSKVONotifying_ClassObject
2021-05-04 10:54:32.415562+0800 ClassDemo[28990:8934226]
objClass1 = 0x10fea3550
objClass2 = 0x10fea3550
objClass3 = 0x600001300240
第三个值变成了NSKVONotifying_ClassObject
2. class对象中的信息
- isa
- superclass
- 属性 property
- instance 方法
- 协议 protocal
- 成员变量,这里的成员变量信息并不是一个 instance 中成员变量的值,而是指在这个 Class 中有哪些成员变量,是 NSSting 的,还是 int 类型的
- 其它
meta-class 对象
1. 获取meta-class对象
在开发中是不会手动去+alloc
一个元类对象,可以通过object_getClass
函数获取class
的isa
类取之,代码如下
- (void)fetchMetaClassWithClassObject:(ClassObject *)clsObj {
//1. 获取一个对象的isa
Class objIsa = object_getClass(clsObj);
//获取元类对象
Class metaClass = object_getClass(objIsa);
NSLog(@" \n metaClass = %p \n metaClass = %@", metaClass, NSStringFromClass(metaClass));
}
会发现,元类还是当前的Class,但是是另一个对象地址
其次,不管是class对象还是元类对象,其类型都是Class,说明在内存结构上是一致的,但是其包含的信息含义是不一样的,其用途也不一样
meta-class 对象中的信息
- isa
- superclass
- 类方法信息
- 其它
对象总结
image
- 总共有三种对象:instance对象、class对象,meta-class对象
- 成员变量的值都存在instance中
- 属性、instance(实例)方法、协议protocol都存于class中
- 类方法都存于meta-class中
二. isa
以上的三种对象是如何关联起来的呢?是通过isa关联的:
instance对象的
isa
的值是class对象,class对象的isa
的值是meta-calss对象
既然实例方法是存在于class对象中,那么当给一个instance对象发送消息的时候,是如何找到具体的实现方法的呢?
当调用实例方法的时候,通过instance对象中的isa找到class,找到对应的实例方法的实现
同理,类方法的调用也是一样的:
当调用类方法时,通过class对象的isa指针找到meta-class, 并找到对应的类方法实现
三. superclass
superclass指针是相对于class对象与meta-class对象来说的
定义两个Class:Person继承于NSObject,Student继承于Person。现在有个场景:通过Student的instance对象调用Person中实现的实例方法,具体的调用过程如下:
通过Student类的instance对象的isa找到对应的Student类的class对象,但没有找到相关的实现,系统会继续到superclass中找,于是会到Person类的class对象中找到具体的实现并调用
类方法的调用也是一样的
四. isa与superclass
在NSObject的头文件中,可以看到一个Class类型的isa成员变量
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
在NSObject的协议里有个Class类型的成员变量superclass
@protocol NSObject
···
@property (readonly) Class superclass;
···
@end
接下来,我们看一个经典的指针逻辑分析图,分别看Objective-C面向对象语言设计、函数调用、成员变量的存储和访问是如何实现的,为什么不能多继承
image这张图可以看到三个类:Rootclass
、Superclass
、SubClass
,分别为基类
、父类
和子类
,图中左到右分别为实例对象
、类对象
、元类
, 举个例子:我们创建一个Person
类继承自NSObject
, 创建一个Student
类继承自Person
。则NSObject
、Peron
、Student
分别对应Rootclass
、Superclass
、SubClass
,左边的Instance对象代表实例对象,也就是我们alloc
出来的实例
由图可知:
isa
- 实例的isa指向类对象,类对象的isa指向元类对象
- meta-class的isa都指向基类的meta-class
superclass
- class的superclass指向父类,如果没有父类,指向nil
- meta的superclass指向父类的meta-class,基类的meta-class的superclass指向基类的class
调用轨迹
- instance对象:isa找到class,方法如果不存在,就通过superclass找父类
- class对象:isa找到meta-class,方法如果不存在,就通过superclass找父类
五. isa、class、superclass关系求证
上面说到一句话
instance对象的isa值是class对象, class的isa值是meta-class对象
上图也有所体现了,再把上图标识序号,如下:
image
接下来就是证明这5条线的正确性,创建Person类继承自NSObject,代码如下:
Person *instanceObj = [[Person alloc] init];
// 第一条线 (instance的isa值是Class对象)
Class classCls = object_getClass(instanceObj);
// 第二条线 (Class对象的isa值是meta-class)
Class metaCls = object_getClass(classCls);
// 第三条线 (meta-class的Superclass 是 RootClass的meta-class)
Class rootMetaCls0 = class_getSuperclass(metaCls);
// 第四条线 (meta-class的isa值是RootClass 的meta-class)
Class rootMetaCls1 = object_getClass(metaCls);
//RootClass的meta-class
Class rootMetaCls = object_getClass(rootMetaCls0);
NSLog(@"\n instanceObj = %p \n classCls = %p \n metaCls = %p \n rootMetaCls0 = %p \n rootMetaCls1 = %p \n rootMetaCls = %p\n", instanceObj, classCls, metaCls, rootMetaCls0, rootMetaCls1, rootMetaCls);
打印结果:
2021-05-04 16:25:08.423125+0800 ClassDemo[32740:9240333]
instanceObj = 0x600002d7c0e0
classCls = 0x109a21800
metaCls = 0x109a217d8
rootMetaCls0 = 0x7fff86d48638
rootMetaCls1 = 0x7fff86d48638
rootMetaCls = 0x7fff86d48638
注意下后边三个值是一样的,现在应该理清了,但是上面的代码中,没有isa
相关的,我们仅仅是获取了对应对象的类型(Class)而已,现在想要看看对应的isa值是多少,由于isa有保护,我们可以通过kvc方式获取,也可以参考写个Class
结构体.通过Bridge强转桥接C/C++再访问isa
对象地址,前提是你要通过clang编译器编译出对象结构体,知道它长什么样。
我们采用kvc的方式,对代码稍作修改,查看isa值:
NSObject *object = [[NSObject alloc] init];
Class rootCls = object_getClass(object);
Person *instanceObj = [[Person alloc] init];
// 第一条线 (instance的isa值是Class对象)
Class classCls = object_getClass(instanceObj);
// 第二条线 (Class对象的isa值是meta-class)
Class metaCls = object_getClass(classCls);
// 第三条线 (meta-class的Superclass 是 RootClass的meta-class)
Class rootMetaCls0 = class_getSuperclass(metaCls);
// 第四条线 (meta-class的isa值是RootClass 的meta-class)
Class rootMetaCls1 = object_getClass(metaCls);
//RootClass的meta-class
Class rootMetaCls = object_getClass(rootMetaCls0);
//root-meta-class 的superclass
Class rootMetaSuperCls = class_getSuperclass(rootMetaCls0);
NSLog(@"\n rootCls = %p \n instanceObj = %p \n classCls = %p \n metaCls = %p \n rootMetaCls0 = %p \n rootMetaCls1 = %p \n rootMetaCls = %p \n rootMetaSuperCls = %p \n", rootCls, instanceObj, classCls, metaCls, rootMetaCls0, rootMetaCls1, rootMetaCls, rootMetaSuperCls);
//通过kvc方式获取isa指针
NSLog(@"\n instanceObj-isa = %p \n classCls-isa = %p \n metaCls-isa = %p \n rootMetaCls0-isa = %p \n", [instanceObj valueForKey:@"isa"], [classCls valueForKey:@"isa"], [metaCls valueForKey:@"isa"], [rootMetaCls0 valueForKey:@"isa"]);
打印结果为:
2021-05-04 16:55:11.688427+0800 ClassDemo[49442:9299385]
rootCls = 0x7fff86d48660
instanceObj = 0x600003e38170
classCls = 0x10e5b4810
metaCls = 0x10e5b47e8
rootMetaCls0 = 0x7fff86d48638
rootMetaCls1 = 0x7fff86d48638
rootMetaCls = 0x7fff86d48638
rootMetaSuperCls = 0x7fff86d48660
2021-05-04 16:55:11.688742+0800 ClassDemo[49442:9299385]
instanceObj-isa = 0x10e5b4810
classCls-isa = 0x10e5b47e8
metaCls-isa = 0x7fff86d48638
rootMetaCls0-isa = 0x7fff86d48638
根据打印结果很明显:
- instance对象的isa指向Class对象(0x10657a808, 第一条线)
- Class对象的isa指向meta-class对象(0x10657a7e0, 第二条线)
- meta-class的Superclass是root-meta-class (0x7fff86d48638, 第三条线)
- meta-class的isa指向 root-meta-class (0x7fff86d48638, 第四条线)
- root-meta-class的isa指向它自己 (0x7fff86d48638, 第五条线)
- root-meta-class的Superclass是Rootclass (0x7fff86d48660)
六. 拓展
由于基元类的Superclass指向基类,我们会发现一个神奇的问题:如果一个类方法一直找不到,发现基类的对象方法存在,则会调用基类的对象方法,也就是子类的一个+方法,调用了基类的一个-方法。这是因为iOS的消息方法机制调用的时候没有区分对象方法和类方法,代码验证一下:
定义一个NSObject类别,里面给NSObject加一个对象方法
.h
@interface NSObject (category)
- (void)showName;
@end
.m
@implementation NSObject (category)
- (void)showName {
NSLog(@"NSObject (category) class = %@", NSStringFromClass([self class]));
}
@end
Person继承自NSObject,在.h声明+ (void)showName
方法不实现(为了编译器不报错,也可以用runtime发消息调用)
.h
@interface Person : NSObject
+ (void)showName;
@end
.m
@implementation Person
@end
调用
[Person showName];
我们在调用Person
的+ (void)showName
方法竟然执行了NSObject category里定义的- (void)showName
方法
2021-05-04 17:09:56.436515+0800 ClassDemo[51930:9318834] NSObject (category) class = Person
这也很好的证明了基元类对象的Superclass指向基类