本文基于objc4-750
源码.
如果我们对Objective-C有所了解, 那么应该知道每个对象都是C语言结构体
, 每个结构体中都会有一个isa
指针, 但是最新版本中isa
已经不再如此:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
// objc-object 的声明
struct objc_object {// 声明 objc_object 对象, 内部有一个 objc_class
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
struct objc_class : objc_object {
// Class ISA; // 继承自 objc_object 类型
Class superclass;// 父类
cache_t cache; // formerly cache pointer and vtable -- 方法缓存
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
...
}
// objc_object 的实现
struct objc_object {
private:
isa_t isa;// 1.可能是 Class, 2.也有可能是 bits
public:
// 因为我们使用结构体取代了原有的 isa 指针,所以要提供一个方法 ISA() 来返回老版本的类指针。 其中 ISA_MASK 是宏定义,这里通过掩码的方式获取类指针, 编译器对直接访问 isa 的操作会有警告,因为直接访问 isa 已经不会返回类指针了,这种行为已经被弃用了,取而代之的是使用 ISA() 方法来获取类指针。
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
...
}
我们可以看到新版本中的isa
不再是一个单纯的指针, 而是一个isa_t
的结构体, 并且这个isa
结构体是private
的. 与此同时, 不仅仅objc_object
有isa
结构体, 每个类objc_class
也有isa
结构体, 因为objc_class
继承自objc_object
.
这里我们得到了一个结论: Objctive-C
中的类也是一个对象.
isa
与元类
这里要引入一个概念元类
, 我们用meta-class
来表示. 下图中标志的非常清楚, 每个对象
的isa
会指向类
, 每个类
的isa
会指向元类
. objc_class
就即可以表现成class 类
, 也可以表现成meta-calss 元类
, 从而使得实例方法的调用和类方法的调用机制达到统一:
- 调用实例对象方法时, 通过实例对象(
objc_object
)的isa
找到类(objc_class
), 从类中获取方法表(method_lists
)从而获取对应的实例方法. - 调用类方法时, 通过类(
objc_class
)的isa
找到元类(meta_class
), 从元类中获取类方法表(method_lists
), 从而获取对应的类方法
isa
的定义
isa_t
的定义如下:
union isa_t {
isa_t() {} // union 的构造函数
isa_t(uintptr_t value) : bits(value) {}
Class cls;
uintptr_t bits;//typedef unsigned long uintptr_t;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
在isa.h
中有如下代码:
# 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)
# 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)
...
#endif
可以清楚的看到, isa_t
和系统的处理器架构有关. 本节用__x86_64__
表示:
struct {
uintptr_t nonpointer : 1; //表示 isa_t 的类型, 是否是指针, 还是
uintptr_t has_assoc : 1; //表示对象是否有关联对象
uintptr_t has_cxx_dtor : 1; //表示对象是否有c++析构函数
uintptr_t shiftcls : 44;//存储Class,Meta-Class对象的内存地址
uintptr_t magic : 6;//被调试器用来从没初始化的东西中区分真是的对象
uintptr_t weakly_referenced : 1;//对象是否有若引用
uintptr_t deallocating : 1;//对象是否在释放
uintptr_t has_sidetable_rc : 1;//对象是否引用计数太大,需要使用sidetable计数
uintptr_t extra_rc : 8;//对象的引用计数是extra_rc 值+1(举例,如果 extra_rc 是5,那么对象的真是引用计数是6)
};
具体解释:
- nonpointer
- 0 代表普通的指针, 当前isa内部存储着Class, Meta-Class对象的内存地址.
- 1 代表优化过, 使用位域存储更多的信息, 当前结构体才有用!!!!
- has_assoc
- 是否有设置过关联对象,如果没有,释放时会更快
- has_cxx_dtor:
- 表示该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象
- shiftcls
- 这里面比较重要的就是shiftcls这个成员变量, 这个成员变量里面存放的就是Class,Meta-Class的地址值, 要取得这个值, 需要bits & ISA_MASK
- magic
- 用于在调试时分辨对象是否未完成初始化
- weakly_referenced
- 是否有被弱引用指向过,如果没有,释放时会更快
- deallocating
- 对象是否正在释放
- has_sidetable_rc
- 当对象引用计数大于一定时,则has_sidetable_rc 的值为 1, 那么引用计数会存储在一个叫 SideTable 的类的属性中
- extra_rc
- 对象的引用计数是extra_rc 值+1(举例,如果 extra_rc 是5,那么对象的真是引用计数是6)
关于对象的引用计数:
在 64 位环境下, pointer优化的 isa 指针并不是就一定会存储引用计数, 毕竟用 8bit 保存引用计数不一定够. 需要注意的是这 8 位保存的是引用计数的值减一. has_sidetable_rc 的值如果为 1, 那么多出来引用计数会存储在一个叫 SideTable 的类的属性中, 当一个对象的引用计数很大时(extra_rc 超出所能表示的范围), 需要它辅助记录对象的引用计数.
此时实际的计数值:retainCount = 1 + extra_rc + sideTable.refcnts[obj]
中的值。
散列表存储引用计数具体使用的DenseMap
实现, objc_object
底层会维护一个 SideTable(DenseMap)
isa_t的初始化
void initIsa(Class cls /*nonpointer=false*/); // 这个是 Isa 的init 方法
/*
initIsa() should be used to init the isa of new objects only.!!!!
这个方法会在[[NSObject alloc] init]; 时候调用
从前面可以知道, 普通对象的isa指向的是对象的类(Class)
*/
inline void
objc_object::initIsa(Class cls) {
// 方法实现
initIsa(cls, false, false);
}
/*
从方法的命名可以看出. 这里初始化的是 Class 的isa!!!
从上面知道, Class的isa应该指向metaClass
*/
inline void
objc_object::initClassIsa(Class cls) {
if (DisableNonpointerIsa || cls->instancesRequireRawIsa()) {
initIsa(cls, false/*not nonpointer*/, false);
} else {
initIsa(cls, true/*nonpointer*/, false);
}
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
//1. 确保当前objc_object 不是 tagged_pointer
assert(!isTaggedPointer());
//2. 在initIsa时, nonpointer = false; 会走第一个{}. 将 cls赋值给 isa.cls
if (!nonpointer) {
isa.cls = cls;
} else {
//3. 如果是 nonpointer = true. 那么需要新建一个 newisa, 然后设置bits 等内容, 然后将objc_object.isa 设置
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
//创建一个新的 isa_t, 用isa_t保存更多的信息
isa_t newisa(0);
// 会进入这里!!! #define ISA_MAGIC_VALUE 0x001d800000000001ULL
//在通过了解内存结构以后, 实际上只是设置了 nonpointer 以及 magic 这两部分的值。 其中 nonpointer 表示 isa_t 的类型, 0表示 raw isa. 也就是没有结构体部分, isa会直接返回一个cls指针. 也就是iOS在64位系统以前的isa类型. 新版本iOS都是64位系统, 目前 nonpointer = 1, 关于类的指针都是保存在 shiftcls
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.bits = ISA_MAGIC_VALUE;
// 在设置 nonpointer 和 magic 值之后,会设置 isa 的 has_cxx_dtor,这一位表示当前对象有 C++ 或者 ObjC 的析构器(destructor),如果没有析构器就会快速释放内存。
newisa.has_cxx_dtor = hasCxxDtor;
//在为 nonpointer, magic 和 has_cxx_dtor 设置之后,我们就要将当前对象对应的类指针存入 isa 结构体中了。将当前地址右移三位的主要原因是用于将 Class 指针中无用的后三位清除减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。
//绝大多数机器的架构都是 byte-addressable 的,但是对象的内存地址必须对齐到字节的倍数,这样可以提高代码运行的性能,在 iPhone5s 中虚拟地址为 33 位,所以用于对齐的最后三位比特为 000,我们只会用其中的 30 位来表示对象的地址。同时OC中类指针的地址后三位也是0, 所以所有类指针16进制的最后一位都是8或者0. 因此在这里右移3位是没有问题的.
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个要点:
- 初始化时候, 需要外部传入参数
nonpointer
, 标志isa是普通指针还是isa_t结构体 -
newisa.bits = ISA_MAGIC_VALUE;
会一次性设置magic
和nonpointer
两个值,nonpointer
设置成1, 表示当前isa_t
是 nonpointer!!! -
newisa.shiftcls = (uintptr_t)cls >> 3;
将class
的地址存储在isa.shiftcls
中.
其中难点在于
shiftcls
存储class或者meta_class的地址, 我们可以打印出的 [NSObject class] 指针, 其最后3bit都是0. 因此源码中右移3位完全正确.
ISA()
新方法
因为我们使用结构体取代了原有的 isa 指针,所以要提供一个方法 ISA()
来返回类指针。
其中 ISA_MASK 是宏定义,这里通过掩码的方式获取类指针:
#define ISA_MASK 0x00007ffffffffff8ULL
inline Class
objc_object::ISA()
{
return (Class)(isa.bits & ISA_MASK);
}
参考
https://github.com/draveness/analyze/blob/master/contents/objc/%E4%BB%8E%20NSObject%20%E7%9A%84%E5%88%9D%E5%A7%8B%E5%8C%96%E4%BA%86%E8%A7%A3%20isa.md#arm64
https://www.jianshu.com/p/7240988c6be6
https://juejin.im/post/5b18f5af5188257d7a49b331
https://www.jianshu.com/p/74db5638f34f
网友评论