研究oc底层之前,先从苹果开源网站下载相应的代码
1:objc_class、objc_object、id、isa
通过代码先了解一下几个结构体的依赖关系,下面介绍isa
typedef struct objc_class *Class;
typedef struct objc_object *id;
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 *` */
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();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
//上面是objc_class部分代码
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
2:isa
在arm64架构之前,isa
仅是一个指针,保存着类对象(Class)
或元类对象(Meta-Class)
的内存地址,在arm64架构之后,苹果对isa
进行了优化,变成了一个isa_t
类型的联合体(union)结构,同时使用位域来存储更多的信息
查看
isa
可以通过object_getClass
方法
一个8字节指针在64位下 其实可以存储很多内容,我们可以优化内存,在不同的位上,放不同的东西! 在这我们还需要补充一下Struct与Union的区别:
1.struct和union都是由多个不同的数据类型成员组成,但在任何同一时刻,union 中只存放了一个被选中的成员, 而struct的所有成员都存在。在struct中,各成员都占有自己的内存空间,它们是同时存在的。一个struct变量的总长度等于所有成员长度之和。在Union中,所有成员不能同时占用它的内存空间,它们不能同时存在。Union变量的长度等于最长的成员的长度
2.对于union的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于struct的不同成员赋值是互不影响的.
通过源码我们发现isa
它是一个位域联合体
,位域联合体
是一个结构占8个字节
,它的特性就是共用内存
,或者说是互斥,比如说如果cls赋值了就不在对bits进行赋值.在isa_t联合体内使用宏ISA_BITFIELD
定义了位域,我们进入位域内查看源码:
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
};
/**
我用的模拟器,所以只看`x86_64`
*/
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
也就是说,我们之前熟知的OC对象的isa指针
并不是直接指向类对象或者元类对象的内存地址,而是需要& ISA_MASK
通过位运算才能获取类对象或者元类对象的地址.
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
//主要是这里,其他判断可以不关注
//从64bit开始,需要经过一次位运算,才能计算出isa真实地址
return (Class)(isa.bits & ISA_MASK);
#endif
}
我们重点看一下shiftcls
,在shiftcls
中存储着类对象和元类对象的内存地址信息,我们上文讲到,对象的isa指针需要同ISA_MASK
经过一次按位与运算才能得出真正的类对象地址.那么我们将ISA_MASK的值0x0000000ffffffff8ULL
转化为二进制数分析一下:
从图中可以看到ISA_MASK的值转化为二进制中有33位都为1,上文讲到按位与运算是可以取出这33位中的值.那么就说明同ISA_MASK进行按位与运算就可以取出类对象和元类对象的内存地址信息. 我们继续分析一下结构体位域中其他的内容代表的含义:
949619A6-DC1C-45DF-8D74-C84C520156DB.png
isa
是OC对象的第一个属性,因为这一属性是来自于继承,要早于对象的成员变量,属性列表,方法列表以及所遵循的协议列表。**经过calloc
申请内存的时候,这个指针是怎么和TCJPerson
这个类所关联的呢?通过分析对象的alloc
方法可以定位到:obj->initInstanceIsa(cls, hasCxxDtor)
,initIsa(cls, true, hasCxxDtor)
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
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
//**目前我们只需要着重分析下面的代码**
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;
}
}
以上{}代码简化后(仅供参考):
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
isa_t newisa(0);
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;
// 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;
}
}
3:isa指向走位分析
`LLDB`相关的指令解析:
1、`x obj`:打印对象内存信息(小端模式);`x/4gx obj`:16进制打印对象前4个字节内存信息
2、`p`:打印对象地址; `p/x`:16进制打印 ;`p/d`:10进制打印;`p/o`:8进制打印; `p/t`:2进制打印
3、`bt`打印当前堆栈信息
对象的内存信息,3段内存地址分布指向:isa(8字节),superclass(8字节),cache(16字节)
(lldb) x/4gx dog
0x1021024b0: 0x001d8001000011e9 0x0000000000000000
0x1021024c0: 0x001dffff888b72b9 0x000000010000078c
对象的isa指针最终指向根元类
25C8F8DE-AC72-4DFC-806F-370A664C5F44.png
还原isa,获取shiftcls
即类对象。实体对象的isa做两次位移>> 3``<< 17
操作,原理请参考ISA_BITFIELD
4、附件
isa和superClass走位图
isa流程图.png69B0409D-2B7B-4988-ABF3-4673A69F1A38.jpeg
网友评论