iOS底层(三)-isa的初始化与指向分析

274 阅读1分钟

1.SEL与IMP

SEL: 相当于一个房子的地址,通过这个地址可以找到你的房子. IMP: 方法的具体实现,也就算相当于你的这栋房子的所有东西

我们开发中经常指派一个button的点击事件selector,实际上就是通过SEL去 找到对应的按钮事件的具体实现

macho - 绑定symbol - 按钮的action(SEL) - 点击事件具体实现(IMP)

2.什么是isa

官方说明: A pointer to the class definition of which this object is an instance. 

一个指向当前实例对象所属类的指针.

我们在创建一个实例对象, 进行alloc的时候, 底层会进行对isa的一个初始化.那么这个实例对象的第一个属性一定是isa.

isa它是来源于NSObject这个类.

3.isa关联对象和类

一个isa既然是当前实例对象所属类的指针, 那么它必然与cls有所关联

首先我们实例化一个对象

MyTest *test = [MyTest alloc];

输出一下这个对象的isa

$1就是isa的二进制数据

再来看一下MyTest这个类的cls

通过表面观察,发现它们后半部分有很相似的地方,但是它们之间具体是怎么关联的呢? 在探索alloc的时候, 我们发现在callAlloc中会有这样一段代码

obj->initInstanceIsa(cls, dtor);

字面意思就是isa的初始化, 进入具体实现

inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    assert(!cls->instancesRequireRawIsa());
    assert(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}

进入initIsa

可以看到底层是创建一个newisa, isa与cls关联就是cls向右平移了三位等于isa的shiftcls属性 那么在之前我们打印的 MyTest二进制数据向右平移三位

还是与之前的isa不太一样. 那么肯定是与isa有关系.

进入isa_t看一下isa的具体结构

发现isa的本质是一个联合体,进入ISA_BITFIELD.

在x86环境下清楚的看到shiftcls占用了44位,shiftcls之前还有3位,这就是cls向右平移3位的原因所在 那么我们只需要shiftcls这一部分 为了方便观察:

可以看到实例化对象的isa中的shiftcls部分与MyTest的cls是一模一样的. 至此,我们就明白isa是如何与cls进行关联.

3.1方法二:

isa既然与cls关联,我们可以通过object_getClass去获取到这个类信息.

进入object_getClass底层实现

继续跟进.

发现最终返回的是isa.bits与上一个蒙板. 在控制台进行同样的操作

两者一致

4.isa的指向分析

4.1类与元类

对象可以创建多个,但是在内存中,类只有一个. 分析:

        MyTest *test = [MyTest alloc];
        Class class1 = [MyTest class];
        Class class2 = [MyTest alloc].class;
        Class class3 = object_getClass([MyTest alloc]);
        Class class4 = test.class;
        NSLog(@"\n%p --- \n%p --- \n%p --- \n%p --- ", class1, class2, class3, class4);

得到结果:

全部都为同一个地址, 这就代表着类只能在内存中存在一个.

我们都知道, 一个对象内存的isa指向的是一个类的内存空间,来打印一下类的内存空间

与实例化对象的内存很是相似,并且发现它的指针地址就是当前类, 那么是否就是代表一个类的内存第一个位置指向了自己. 其实不是的,假如是指向了自己,那么就一定会出问题. 这个东西的真实身份就是**元类(meta class)**.

总结一下概念: 对象: 我们根据类来实例化出来的. : 我们代码写出来的,它在内存中只存在一份.肯定不是我们创建出来的,是系统通过我们的代码创建了类. 元类: 系统在编译的时候,发现有了这么一个类,就为了方便,就会在中间产生了另外一种结构, 就是元类.我们是实例化不出来的,是由编译器编译阶段产生的.


4.2元类的指向

我们都知道对象的isa会指向一个类对象, 类对象又是指向元类. 那么元类指向哪里呢? 我们可以通过获取类的方法来探索, 实际上就上探索isa的走位. 既然知道一个类的第一个位置是指向元类

此时$2就代表元类, 继续往下走找元类的isa:

发现元类的isa指向的是NSObject,但是NSObject类的指针明显与其不一样, 那么可以得到一条信息, 元类是指向NSObject的元类, 来验证一下. 打印NSObject的元类:

与上面一致,那么可以确定的是 元类是指向NSObject的元类,也就是指向根元类 再来找一下根元类的指向.

发现**根元类最终是指向了自身根元类**, 形成一个闭环


总结: isa走位: 实例对象 -> 类 -> 元类 -> 根元类 <-> 根元类 例: test -> MyTest -> MyTest(元类) -> NSObject(元类) <-> NSObject(元类)

附图: