我们使用的所有对象的都是继承至NSObject, 你是否会探索下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
}
点击 Class
可以可以发现是结构体objc_class
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif
继续点击objc_class
可以发现定义的方式, 发现继承至 objc_object
, 通过上面定义的 objc_object
结构体可以看到只有一个 isa
变量
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits;
....
}
通过objc4源码中找到定义结构体 objc_object
, 可以发下也只有一个私有的变量 isa
, 那么isa
到底是什么呢?或者说 干了什么
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
. . .
}
接续查找 isa_t
发现定义 isa
类型的结构是一个联合体(union 也称为共用体)
这里补充下知识点 结构体 和 联合体
构造数据类型的方式分为两种方式:
- 结构体 (struct)
- 联合体 (union, 也称为共用体)
1. 结构体
结构体是指把不同的数据组合成一个整体,其变量是共存的
,变量不管是否使用,都会分配内存。
【缺点】:所有属性都分配内存,比较浪费内存,假设有4个int成员,一共分配了16字节的内存,但是在使用时,你只使用了4字节,剩余的12字节就是属于内存的浪费
【优点】:存储容量较大,包容性强,且成员之间不会相互影响
2. 联合体
联合体也是由不同的数据类型组成,但其变量是互斥的
,所有的成员共占一段内存。而且共用体采用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会将原来成员的值覆盖掉
【缺点】:包容性弱,
【优点】:所有成员共用一段内存,使内存的使用更为精细灵活,同时也节省了内存空间
两者的区别
-
内存占用情况
- 结构体的各个成员会占用不同的内存,互相之间没有影响
- 共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员
-
内存分配大小
- 结构体内存 >= 所有成员占用的内存总和(成员之间可能会有缝隙)
- 共用体占用的内存等于最大的成员占用的内存
isa 的类型 isa_t
通过联合体 (union)
定义的 isa_t
类型, 其中 cls
和 bits
是互斥的,可以通过前面分析alloc & init探索 中查看具体赋值。 其中 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
};
isa_t类型
使用联合体的原因也是基于内存优化
的考虑,这里的内存优化是指在isa指针
中通过char + 位域
(即二进制中每一位均可表示不同的信息)的原理实现。通常来说, isa指针
占用的内存大小是8字节,即64位
,已经足够存储很多的信息了,这样可以极大的节省内存,以提高性能。
bits
存放的类的更多信息可以通过 宏定义 ISA_BITFIELD
中查看
--------------- arm64 架构 ----------------
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
--------------- 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)
-
nonpointer :
表示优化的指针, 看这个Tagged Pointer 优化轻量级的内存,代表优化的指针, nonpointer
·0
: 代表普通的指针(纯isa指针),存储着Class、Meta-Class
对象的内存地址
·1
: 代表优化过,包含了类的一些信息、对象的引用计数等 -
has_assoc :
表示 对象含有或者曾经含有关联引用(关联对象)
,没有关联引用的可以快速释放内存。 -
has_cxx_dtor :
表示当前对象是否有C++或者Objc的析构器(类似于dealloc, dispose), 如果没有析构器就会快速释放内存 -
shiftcls :
存储着Class、Meta-Class对象的内存地址信息,即类的信息,
· 其中在arm64
架构下占用 33 位,在x86_64
中占用44位 -
magic :
用于在调试时分辨对象是否初始化,占用 6 位 -
weakly_referenced :
是否有被弱引用指向过,如果没有,释放时会更快。
· 指对象是否被指向
或者曾经指向一个ARC的弱变量
-
deallocating :
对象是否正在释放 -
has_sidetable_rc :
如果是1
对象的引用计数太多了,存不下了,
· 如果为1 ,引用计数器过大无法存储在isa
中
· 那么引用计数会存储在一个叫SideTable (散列表)
的类的属性中 -
extra_rc :
额外引用计数,如果has_sidetable_rc
为 1,会存储在这个里面
· 里面存储的值是引用计数器减1
· 如果对象的引用计数为10,那么extra_rc为9
image.png
注:上面分析的是在arm64
架构下的shiftcls
占33位,在x86_64
下构下shiftcls
占44位
isa 与 类 的关联
cls
与isa
关联原理就是isa指针中的shiftcls位域
中存储了类信息,其中initInstanceIsa
的过程是将 calloc 指针 和当前的 类cls 关联起来,有以下几种验证方式:
【方式一】通过initIsa函数中的newisa.shiftcls = (uintptr_t)cls >> 3验证
【方式二】通过isa指针地址与ISA_MSAK 的值 & 来验证
【方式三】通过runtime的方法object_getClass验证
【方式四】通过位运算验证
方式一: 通过initIsa函数初始化赋值
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)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;
}
}
例如自定义类MGPerson
初始化是也会调用 initIsa ()
函数,接下来分析 isa
和类
的关系
在248行给isa
的shiftcls
赋值MGPerson
类的,这里就是将 cls
类 和 isa
指针通过shiftcls
位域关联,存储着类的信息
方式二: 通过 isa & ISA_MSAK
-
执行
x/4gx person
得到isa指针的地址0x001d80010000145d -
将
isa指针地址 & ISA_MASK
-
arm64中,ISA_MASK 宏定义的值为0x0000000ffffffff8ULL
-
x86_64中,ISA_MASK 宏定义的值为0x00007ffffffffff8ULL
-
方式三:通过 object_getClass 获取类和isa 关联
通过查看object_getClass的源码
实现,同样可以验证isa与类关联的原理
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA();
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
image.png
执行到174行会发现和方式二的原理一样,
方式四:通过位运算
-
其中
x/4gx person
得到isa
指针的地址 0x001d80010000145d -
上面分析isa的结构可知右移3位是
nonpointer
,has_assoc :
,has_cxx_dtor :
,并没有存储类的信息, 所以抹去最后三位, 即右移3位 -
因为中间44位存储着类的信息(前面分析的x86_64 架构下isa)所以高 17位没有需要抹除,加上第一步右移3位, 一共需要左移20位
-
0x0002000028b00000 右移17位复原
shiftcls
的位置, 等到最终的类的地址 -
执行打印 0x0000000100001458地址 等到
MGPerson
类
网友评论