前言
我们都知道对象由类实例化而来,在上一篇《OC底层系列二》-对象中,我们知道对象是一个objc_object类型的结构体,类是一个objc_class类型的结构体,今天我们从底层来探究对象和类是如何建立关联。
目录
0、简介
- 本文主要探索OC对象和类之间如何建立关联,并进行验证。
1、对象和类的定义
- 我们再从源码角度看看对象和类的定义
typedef struct objc_class *Class; // 类的定义
typedef struct objc_object *id; // 对象的定义
1.1、objc_object
// objc-private.h
// objc_object 的实现
struct objc_object {
private:
isa_t isa;
以下是一些函数,省略
...
}
1.2、objc_class
// objc-runtime-new.h
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
// objc_class的部分方法代码省略不贴出来
}
- 从源码中我们看到objc_class继承自objc_object,说明了类也是一个对象, objc_class继承自objc_object,自然也就继承了objc_object的成员,则objc_class也拥有一个isa,由此我们推断对象和类的关联在于isa。
2、isa
// objc-private.h
// isa_t的定义,一个联合体,值为cls或者bits
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
2.1、isa的初始化
- objc_object中初始化isa方法源码如下,如下:
// objc_private.h
struct objc_object {
private:
isa_t isa;
public:
···
private:
void initIsa(Class newCls, bool nonpointer, bool hasCxxDtor);
···
}
// objc-object.h
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
// 2.1.1
isa.cls = cls;
} else {
// 2.1.2
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
// 2.1.3
assert(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
// 2.1.4
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
- 通常我们创建的OC对象都是nonpointer,所以会执行2.1.2,创建一个isa_t类型的newisa并初始化其成员bits为0。
- SUPPORT_INDEXED_ISA定义如下,根据注释,其表示 isa_t 中存放的 Class 表示的是否为Class 的地址,是一个索引(根据该索引可在类信息表中查找该类结构地址)。
// objc-config.h
// Define SUPPORT_INDEXED_ISA=1 on platforms that store the class in the isa
// field as an index into a class table.
// Note, keep this in sync with any .s files which also define it.
// Be sure to edit objc-abi.h as well.
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
- 我们来分析一下SUPPORT_INDEXED_ISA宏定义的值,__ARM_ARCH_7K__,从名称上看其表示在 ARM 7k 架构 CPU 的代码中的标志宏,我们可以在LLVM 开源的ARM.cpp找到其定义:
// ARM.cpp
// Unfortunately, __ARM_ARCH_7K__ is now more of an ABI descriptor. The CPU
// happens to be Cortex-A7 though, so it should still get __ARM_ARCH_7A__.
if (getTriple().isWatchABI())
Builder.defineMacro("__ARM_ARCH_7K__", "2");
通过注释和代码可以知道,这个值只有在iWatch下才会被定为2,换句话说__ARM_ARCH_7K__代表的是iWatch的宏。由此可知,在iPhone模拟器以及真机上__ARM_ARCH_7K__值为FALSE。
__arm64__表示是否采用arm64架构。在模拟器上为FALSE,真机上为TRUE。
__LP64__ 表示在64位机器上,如果int是32位,long是64位,pointer也是64位,那么该机器就是__LP64__。在模拟器上和真机上__LP64__为TRUE。
综上,ARM_ARCH_7K >= 2 || (arm64 && !LP64)
=FALSE || FALSE
=FALSE
所以SUPPORT_INDEXED_ISA的值为FALSE。因此初始化isa方法走的是2.1.4
// 2.1.4
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
2.2、shiftcls
shiftcls的值是isa中cls的值右移三位得到,shiftcls存储类指针的值。根据shiftcls的宏定义,在 x86_64 架构有 44位 用来存储类指针,arm64 架构中有 33位 。
-
我们在x86_64(模拟器下)为研究,ISA_BITFIELD定义如下:
shiftcls的值为二进制的3~46位。
我们通过LLDB验证shitfcls是否存储着cls信息,添加如下代码并在NSLog处添加断点:
#import <Foundation/Foundation.h>
// Animal继承NSObject,没有添加任何成员
#import "Animal.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Animal *animal = [Animal alloc];
NSLog(@"Hello, World!");
}
return 0;
}
-
在控制台使用LLDB如下操作:
- x/4gx 打印对象anmial的内存信息,以16进制格式打印4个8字节。其第一个8字节为isa。
- p/t animal 将[animal class]的值以二进制格式输出。
格式化二进制如下:
// [animal class]
$0 = 0b 0000 0000 0000 0000 00 00 0000 0000 0001 0000 0000 0000 0000 0010 0000 1111 0 000
// isa
$2 = 0b 0000 0000 0001 1101 10 00 0000 0000 0001 0000 0000 0000 0000 0010 0000 1111 0 001
- 对上面打印结果格式化可以看到,[animal class]的3~46位和isa的3~46位位是一样的,验证了对象的关于类的信息是存在在isa中的shiftcls中。
3、[object class]方法分析
- [animal class]中class的底层实现:
\\ objc-object.h
inline Class
objc_object::ISA()
{
ASSERT(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
// 3.1
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
// 3.2
return (Class)(isa.bits & ISA_MASK);
#endif
}
- SUPPORT_INDEXED_ISA值为FALSE,所以走//3.2,返回(Class)(isa.bits & ISA_MASK);
// isa.h
define ISA_MASK 0x00007ffffffffff8ULL
4、通过LLDB验证对象和类如何关联
-
我们根据isa.bits & ISA_MASK使用LLDB进行验证,继续如下操作
$3即ISA_MASK的二进制表示,$6即(isa.bits &ISA_MASK)的结果,最后我们d打印$6的描述为Animal,验证完毕。
5、总结
- 对象的类信息存储在其isa中的的shiftcls中,对象和类的通过对象中的ias的shiftcls进行关联,class方法本质上返回的是isa的shiftcls,其底层实现通过isa.bit& ISA_MASK获取到shiftcls的值。
- 我们可以理解为类实例化的对象的isa指向该类。
6、TODO
- OC对象的isa(其位域成员shiftcls)中的存着类的信息,OC类也是一个对象,那么OC类的isa(其位域成员shiftcls)存储着什么呢?
- 源码中objc-class有一个superclass,我们可以推测superclass代表当前类的父类,Animal的superclass应该为NSObject类,那么NSObjec类的superclass是什么?
- 这些我会在下一篇文章中继续探究。