iOS - isa、superclass指针,元类supercl

2021-05-04  本文已影响0人  码代码的小马

本文已同步至掘金:iOS - isa、superclass指针,元类superclass指向基类本身

一. 对象

instance对象,也称实例对象
class对象,也称类对象
meta-calss对象,也称元类对象

本文将探讨如下问题:

  • 这些对象中包含了什么信息
  • 这些信息如何关联
  • 成员变量是存在哪里的
  • 类方法与实例方法是存在哪里的
  • 如何找到superclass

instance对象

1. 什么是instance对象

通过+alloc之后的都是instance对象.这里还需要强调的是:instance对象不仅仅是NSObject对象,还有一个代理类NSProxy,也能创建instance对象

// 创建一个对象
NSObject* obj = [NSObject alloc];

Class对象

1. 获取class对象

创建一个继承自NSObjectClassObject

- (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对象中的信息

  1. isa
  2. superclass
  3. 属性 property
  4. instance 方法
  5. 协议 protocal
  6. 成员变量,这里的成员变量信息并不是一个 instance 中成员变量的值,而是指在这个 Class 中有哪些成员变量,是 NSSting 的,还是 int 类型的
  7. 其它

meta-class 对象

1. 获取meta-class对象

在开发中是不会手动去+alloc一个元类对象,可以通过object_getClass函数获取classisa类取之,代码如下

- (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 对象中的信息

  1. isa
  2. superclass
  3. 类方法信息
  4. 其它

对象总结

  • 总共有三种对象:instance对象、class对象,meta-class对象
  • 成员变量的值都存在instance中
  • 属性、instance(实例)方法、协议protocol都存于class中
  • 类方法都存于meta-class中
image

二. 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

这张图可以看到三个类:RootclassSuperclassSubClass,分别为基类父类子类,图中左到右分别为实例对象类对象元类, 举个例子:我们创建一个Person类继承自NSObject, 创建一个Student类继承自Person。则NSObjectPeronStudent分别对应RootclassSuperclassSubClass,左边的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

根据打印结果很明显:

六. 拓展

由于基元类的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指向基类

上一篇下一篇

猜你喜欢

热点阅读