上一篇文章,咱们介绍了OC目标的分类,它们内存中存放的信息如下图:

iOS-底层-isa指针和superclass指针+窥探Class

可是这个isa和superclass有什么用呢?

首先咱们创建两个类,如下:

// MJPerson
@interface MJPerson : NSObject <NSCopying>
{
    @public
    int _age;
}
@property (nonatomic, assign) int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end
@implementation MJPerson
- (void)test
{
}
- (void)personInstanceMethod
{
}
+ (void)personClassMethod
{
}
- (id)copyWithZone:(NSZone *)zone
{
    return nil;
}
@end
// MJStudent
@interface MJStudent : MJPerson <NSCoding>
{
@public
    int _weight;
}
@property (nonatomic, assign) int height;
- (void)studentInstanceMethod;
+ (void)studentClassMethod;
@end
@implementation MJStudent
- (void)test
{
}
- (void)studentInstanceMethod
{
}
+ (void)studentClassMethod
{
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
    return nil;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
}
@end

其中MJStudent承继于MJPerson

一. isa指针的效果

调用办法:

MJPerson *person = [[MJPerson alloc] init];
person->_age = 10;
[person personInstanceMethod];
[MJPerson personClassMethod];

咱们都知道办法调用的实质是发送音讯,所以上面的办法调用的实质便是给person目标发送personInstanceMethod音讯,给MJPerson类目标发送personClassMethod音讯

他们底层是这样的:(也能够经过NSObject的实质介绍的指令重写为c++文件检查)

调用目标办法实质
objc_msgSend(person, @selector(personInstanceMethod))
调用类办法的实质 (objc_msgSend内部是经过汇编完成的,半开源)
objc_msgSend([MJPerson class], @selector(personClassMethod))

可是现在有一个问题, personInstanceMethod目标办法不在person实例目标里边, personInstanceMethod类办法也不在MJPerson类目标里边,怎么办?

其实他们是经过isa指针联系起来的:

  1. instance的isa指向class 当调用目标办法时,经过instance的isa找到class,最终找到目标办法的完成进行调用
  2. class的isa指向meta-class 当调用类办法时,经过class的isa找到meta-class,最终找到类办法的完成进行调用

现在咱们就理解了: [person personInstanceMethod]办法调用的流程: person实例目标先经过自己的isa找到MJPerson类目标,然后在MJPerson类目标里边找到personInstanceMethod办法进行调用

[MJPerson personClassMethod]办法的调用流程: MJPerson类目标先经过自己的isa找到MJPerson的元类目标,然后在元类目标里边找到personInstanceMethod办法进行调用

如下图:

iOS-底层-isa指针和superclass指针+窥探Class

二. superclass指针的效果

先看定论:

iOS-底层-isa指针和superclass指针+窥探Class

1. 解释上图定论

关于isa指针:
  1. instance的isa指向class
  2. class的isa指向meta-class
  3. meta-class的isa指向基类(NSObject)的meta-class
  4. 基类(NSObject)的meta-class的isa指向它自己
关于superclass指针:
  1. class的superclass指向父类的class
  2. 假如没有父类,superclass指针为nil (最终一向找不到办法会报错:unrecognized selector sent to instance/class)
  3. meta-class的superclass指向父类的meta-class
  4. 基类的meta-class的superclass指向基类的class
instance调用目标办法的轨道:

实例目标的isa找到class,办法不存在,就经过superclass找父类

class调用类办法的轨道:

类目标的isa找meta-class,办法不存在,就经过superclass找父类

2. 办法调用轨道剖析

① [student personInstanceMethod]子类实例目标调用父类的目标办法

当Student的instance目标要调用Person的目标办法时,会先经过实例目标的isa找到Student的class,然后经过它的superclass找到Person的class,最终找到目标办法的完成进行调用

iOS-底层-isa指针和superclass指针+窥探Class

② [MJStudent personClassMethod]子类类目标调用父类的类办法

当Student的class要调用Person的类办法时,会先经过Student的class的isa找到Student的meta-class,然后经过它的superclass找到Person的meta-class,最终找到类办法的完成进行调用

iOS-底层-isa指针和superclass指针+窥探Class

补充:

假如MJStudent和MJPerson里边都有一个test目标办法,调用办法:[student test],假如按照面向目标的逻辑调用的是student的test,当理解isa和superclass就知道为什么调用的是student的test了。

小使命:

– (instancetype)init办法在NSObject的类里边,想一下调用进程 [student init];

+ (void)load在NSObject的元类里边,想一下调用进程 [MJStudent load];

三. 验证:基类的meta-class的superclass指向基类的class

下面咱们验证一条最特别的实线:基类的meta-class的superclass指向基类的class

给NSObject增加分类,只完成一个test目标办法,MJPerson不完成任何办法。

- (void)test
{
    NSLog(@"-[NSObject test] - %p", self);
}

执行以下代码

NSLog(@"[MJPerson class] - %p", [MJPerson class]);
NSLog(@"[NSObject class] - %p", [NSObject class]);
[MJPerson test];
[NSObject test]

打印成果:

[MJPerson class] - 0x1000011e0
[NSObject class] - 0x7fff97e99140
-[NSObject test] - 0x1000011e0
-[NSObject test] - 0x7fff97e99140

能够发现,无论是MJPerson仍是NSObject调用test办法后,最终都会调用NSObject的目标办法

**进程:**MJPerson类目标调用test办法的时分会经过MJPerson的isa去NSObject元类目标里边找test办法(元类目标里边放的都是类办法),可是没找到,然后就去NSObject元类目标的类目标里边找test办法,然后就找到了- (void)test办法,然后就调用了。

能够发现,不管是类目标仍是实例目标,调用流程都是: isa -> superclass -> suerpclass -> superclass -> …. superclass -> nil

总结:

所以,目标办法调用轨道为:

iOS-底层-isa指针和superclass指针+窥探Class

类办法调用轨道为:

iOS-底层-isa指针和superclass指针+窥探Class

四. 关于ISA_MASK

上面咱们说了实例目标的isa指向类目标,类目标的isa指向元类目标,其实在64位之前的确是这样的,可是64位之后,实例目标的isa & ISA_MASK = 类目标,类目标的isa & ISA_MASK = 元类目标(便是需求做个位运算)。

下面咱们验证一下,首先在objc4源码中查找ISA_MASK咱们会发现以下代码:

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL

上面代码的意思是,假如是arm64平台用第一个,x86平台用第二个,由于咱们代码是命令行的,所以用第二个,验证代码如下:

struct mj_objc_class {
    Class isa;
    Class superclass;
};
MJPerson *person = [[MJPerson alloc] init];
Class personClass = [MJPerson class];
struct mj_objc_class *personClass2 = (__bridge struct mj_objc_class *)(personClass);
Class personMetaClass = object_getClass(personClass);
NSLog(@"%p %p %p", person, personClass, personMetaClass);

为什么要定义mj_objc_class结构体? 由于Class的虽然有isa,可是没有显露出来,假如咱们直接拜访会报错

(lldb) p/x (long)personClass->isa
error: member reference base type 'Class' is not a structure or union

咱们重新换个结构体,指向personClass就能获取内部的isa指针。

上面代码,打印成果为: 实例目标:0x100603ac0 类目标:0x1000014d0 元类目标:0x1000014a8

打断点,打印如下:

iOS-底层-isa指针和superclass指针+窥探Class

其中,p/x是按照16进制打印,person->isa是直接拜访指针指向的地址。 除了打印还能够直接在点击enter,即可出现地址,如上图

观察上图,验证了咱们所说的,实例变量的isa和ISA_MASK做个位运算才是类目标的地址值,类目标的isa和ISA_MASK做个位运算才是元类目标的地址值。

iOS-底层-isa指针和superclass指针+窥探Class

那么superclass是不是也需求做位运算呢? 测验代码如下:

struct mj_objc_class *personClass = (__bridge struct mj_objc_class *)([MJPerson class]);
struct mj_objc_class *studentClass = (__bridge struct mj_objc_class *)([MJStudent class]);
NSLog(@"1111");

打断点,成果如下:

iOS-底层-isa指针和superclass指针+窥探Class

能够发现,子类的supreclass指针里边存的直接是父类的地址,并不需求做位运算。

五. 窥视Class结构

类目标和元类目标都是Class类型的,所以他们在内存中的结构也是相同的,只不过存放的信息不相同,下面咱们就窥视Class的结构。

点击Class,进去之后咱们发现,Class的定义是这样的,是个结构体。

// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

再点击objc_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;
/* Use `Class` instead of `struct objc_class *` */

上面有咱们想要的信息,可是上面的结构体在最新的OBJC2已经过时了(OBJC2_UNAVAILABLE),所以只能咱们自己检查源码了。

在objc4中,查找struct objc_class,找到如下代码:

//class和metra-class目标的实质结构
//objc_class : objc_object  这是c++结构体的承继(c++结构体和类几乎没差异)
struct objc_class : objc_object {
    // Class ISA; // isa
    Class superclass; //superclass
    cache_t cache;  // 办法缓存
    class_data_bits_t bits; // 用于获取详细的类信息
    class_rw_t *data() { 
        return bits.data();
    }
......
}

再次查找”struct objc_object {“,能够找到父类

struct objc_object {
private:
    isa_t isa;
......
};

能够发现父类里边就一个isa,其他的都是办法,现在isa和superclass咱们都找到了。

再次检查objc_class结构体,发现能够经过bits获取data信息,点击它的类型class_rw_t(r是read,w是write,t是table,整体是可读可写的表的意思),进去

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro; //只读表
    method_array_t methods; //办法列表(包括分类里边的办法)
    property_array_t properties; //特点列表
    protocol_array_t protocols; //协议列表
......
}

这儿咱们能够找到特点列表,协议列表,办法列表。

再次点击class_ro_t(只读表),进去

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize; //instance目标占用的空间
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name; //类名
    method_list_t * baseMethodList; //本来类里边的办法列表(不包括分类),所以是只读的
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars; //成员变量列表
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

这儿咱们找到了成员变量列表

至此,isa、superclass、特点、成员变量、协议、目标办法咱们都找到了。

看图总结一下结构:

iOS-底层-isa指针和superclass指针+窥探Class

iOS-底层-isa指针和superclass指针+窥探Class

总结:

现在咱们理解了,无论是类目标仍是元类目标,他们在内存中的结构都是objc_class这种结构体类型的,可是他们存储的信息不相同,比如对于元类目标,只有isa、superclass、类办法有值,其他的都是空。

六. 验证Class内存结构

上面咱们是在源码中剖析了Class和meta-Class内存结构,可是实际上是不是这样咱们还需求在代码中进行验证。

如何在代码中验证?就像上面咱们想检查isa相同,仿照体系自定义了一个结构体。在这儿咱们也是仿照体系的结构体自定义咱们自己的结构体,然后把体系的结构体指向咱们自己的结构体,假如他们内存结构相同,那么强制转换类型以后数据肯定都能够对的上。代码如下:

//#import "MJClassInfo.h"是c++文件
//假如文件名是main.m编译的时分只能知道OC和C代码,假如改成main.mm才知道c++文件
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJStudent *stu = [[MJStudent alloc] init];
        stu->_weight = 10; 
        mj_objc_class *studentClass = (__bridge mj_objc_class *)([MJStudent class]);
        mj_objc_class *personClass = (__bridge mj_objc_class *)([MJPerson class]);
        class_rw_t *studentClassData = studentClass->data();
        class_rw_t *personClassData = personClass->data();
        class_rw_t *studentMetaClassData = studentClass->metaClass()->data();
        class_rw_t *personMetaClassData = personClass->metaClass()->data();
        NSLog(@"1111");
    }
    return 0;
}

打断点,检查类目标结构如下:

iOS-底层-isa指针和superclass指针+窥探Class

元类目标结构如下:

iOS-底层-isa指针和superclass指针+窥探Class

成果正如所料,下面咱们就能回答面试题了。

问题二. 目标的isa指针指向哪里?

  1. instance的isa指向class
  2. class的isa指向meta-class
  3. meta-class的isa指向基类(NSObject)的meta-class
  4. 基类(NSObject)的meta-class的isa指向它自己

问题三. superclass指针指向哪里?

  1. class的superclass指向父类的class
  2. 假如没有父类,superclass指针为nil (最终一向找不到办法会报错:unrecognized selector sent to instance/class)
  3. meta-class的superclass指向父类的meta-class
  4. 基类的meta-class的superclass指向基类的class

问题四. OC的类信息存放在哪里?

  1. 特点、成员变量、协议、目标办法,存放在class目标中
  2. 类办法,存放在meta-class目标中
  3. 成员变量的详细值,存放在instance目标中

Demo地址: isa和superclass