Class, id, NSObject
对象到底是什么, id 是什么, Class又是什么, 平常我们可以id a, 也可以Class a去接收一个对象.由上图可知:
- objc_class继承自objc_object的结构体
- objc_object内部有一个Class类型的实例isa
- Class是一个objc_class结构体指针
- id是一个objc_object结构体指针
在001篇章中探索alloc的时候, 在_class_createInstanceFromZone方法中, 返回的obj是id类型的obj, 也就是返回了一个objc_object *的指针.
所以, 其实id和Class是一个东西, 但是id的权限和范围更广泛, 更大.
图一对应的实际的可以是:
看图04如下:
实际上isa是一个isa_t的一个东西, 实际上isa_t是什么呢, 我们先回到我们alloc创建的流程中, 看一下_class_createInstanceFromZone方法:
根据指引我们看一下initIsa做了点什么:
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#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
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
// 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;
}
看了这段代码, 我感兴趣的地方有三个部分
- ASSERT(!isTaggedPointer());
- isa_t
- nonpointer
isTaggedPointer
inline bool
objc_object::isTaggedPointer()
{
return _objc_isTaggedPointer(this);
}
//# define _OBJC_TAG_MASK 1UL
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
这段代码明显的意思就是, 当前的地址的64位低地址第一位是否是1, 如果是1 , 就是TaggedPointer如果不是1就不是. TaggedPointer其实是苹果对内存的一种.
苹果预留了环境变量OBJC_DISABLE_TAGGED_POINTERS,通过设置该变量的布尔值,可以将Tagged Pointer技术的启用与关闭的决定权交给开发者!如果禁用Tagged Pointer,只需设置环境变量OBJC_DISABLE_TAGGED_POINTERS为YES即可!
//thread backtrace 打印堆栈
Tagged Pointer
上面文章中写的是最高位判断, 但是我们最新的源码逻辑来看是最低位是否是1. 后续继续玩一下tag pointer.
2021-06-15 16:27:38.440812+0800 KCObjcBuild[10926:386502] number1 pointer is 0x96eff9fc62cfdc01
2021-06-15 16:27:38.441537+0800 KCObjcBuild[10926:386502] number2 pointer is 0x96eff9fc62cfdf01
2021-06-15 16:27:38.441696+0800 KCObjcBuild[10926:386502] number3 pointer is 0x96eff9fc62cfde01
我使用NSNumber创建了@1,@2,@3小数值的数据, 结果高低位都是1, 可以猜测一下到底是高位标识还是低位标识, 或者思考一下如果是你 你会选择高位标识还是低位标识.
isa_t
先看下代码的标识, 无用的就先处理掉了.
//uintptr_t就是无符号long, 8字节. 下面
typedef unsigned long uintptr_t;
union isa_t {
//构造方法
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
//位域
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
...
#endif
...
};
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# 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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
- union中 bits和结构体ISA_BITFIELD共用64字节, 存储一些isa信息的额外值.
我们以模拟器的x86_64为例
- nonpointer :是否是纯指针, 是否有优化, 0为纯指针, 1为优化
- has_assoc :是否关联对象, 0没有, 1有
- has_cxx_dtor :是否有析构函数, 有析构函数需要做一些处理逻辑, 没有析构函数, 可以更快的释放对象.
- shiftcls :真正的class, 存储类的指针的值. 模拟器44, 手机33位.
- magic 用于判断当前对象是真的对象, 还是未初始化的.
- weakly_referenced 是否有弱引用计数, 如果有的话要从弱引用表里移除, 没有的话可以更快的释放.
- unused 标志对象是否正在释放内存(摘要过来, 没验证)
- has_sidetable_rc 引用计数大于2^8需要该变量存储进位.
- extra_rc 表示该对象的引用计数值, 实际上是引用计数值-1.
并不知道怎么对unused, magic进行考究验证, 后续查查资料在看看到底是个什么作用.
对extra_rc进行验证, 主要是因为extra_rc只有8位, 所以我在考虑的时候考虑的是超过了8位之后, 引用计数会怎么样呢?
下图是引用计数为301的情况
想要取shiftcls, 系统提供的就是对一个mask宏值进行与运算, 实际上就是对0b(17个0)(44个1)(3个0)进行与运算, 然后运算后的数据 就是真实的shiftcls数据了, 直接打印就是NSObject等类名.
系统现在有两种处理方式
- 直接与一个值进行与运算得到想要的地址数据, 位域最合适
- 直接右移, 左移把额外的数据挤出去也可以.
单单isa_t来说, 其实是与运算最方便.
nonpointer
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
if (!nonpointer) {
//只处理类的本身把class塞进去
newisa.setClass(cls, this);
} else {
//做一系列的处理
}
...
}
上述代码初始化nonpointer一直都是false, nonpointer其实标识的意思大致是是否是单纯的指针, 如是果是就直接setClass处理了, 如果不是就进入下面的对很多isa内部数据进行赋值操作等. 看上面的isa_t就了解了
总结
目前还未对magic, unused, 以及isTaggetPointer进行验证.
还是有很多不足吧, 但是时间又不多 , 写文章实在太慢了, 等知识成体系了在好好构一个框架.
继续努力吧, 感谢哭泣又一个坑, 明明extra_rc超过256才会失效, 文档写个10, 还能是失误写错了? 请你善良点朋友.