引用计数如何存储
有些对象如果支持使用 TaggedPointer,苹果会直接将其指针值作为引用计数返回;如果当前设备是 64 位环境并且使用 Objective-C 2.0,那么“一些”对象会使用其 isa
指针的一部分空间来存储它的引用计数;否则 Runtime 会使用一张散列表来管理引用计数。
其实还有一种情况会改变引用计数的存储策略,那就是是否使用垃圾回收(用UseGC
属性判断),但这种早已弃用的东西就不要管了,而且初始化垃圾回收机制的 void gc_init(BOOL wantsGC)
方法一直被传入 NO
。
TaggedPointer
判断当前对象是否在使用 TaggedPointer 是看标志位是否为 1 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#if SUPPORT_MSB_TAGGED_POINTERS # define TAG_MASK (1ULL<<63) #else # define TAG_MASK 1 inline bool objc_object::isTaggedPointer() { #if SUPPORT_TAGGED_POINTERS return ((uintptr_t)this & TAG_MASK); #else return false; #endif } |
id
其实就是 objc_object *
的简写(typedef struct objc_object *id;
),它的 isTaggedPointer()
方法经常会在操作引用计数时用到,因为这决定了存储引用计数的策略。
isa 指针(NONPOINTER_ISA)
用 64 bit 存储一个内存地址显然是种浪费,毕竟很少有那么大内存的设备。于是可以优化存储方案,用一部分额外空间存储其他内容。isa
指针第一位为 1 即表示使用优化的 isa
指针,这里列出不同架构下的 64 位环境中 isa
指针结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; #if SUPPORT_NONPOINTER_ISA # if __arm64__ # define ISA_MASK 0x00000001fffffff8ULL # define ISA_MAGIC_MASK 0x000003fe00000001ULL # define ISA_MAGIC_VALUE 0x000001a400000001ULL struct { uintptr_t indexed : 1; uintptr_t has_assoc : 1; uintptr_t has_cxx_dtor : 1; uintptr_t shiftcls : 30; // MACH_VM_MAX_ADDRESS 0x1a0000000 uintptr_t magic : 9; 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 0x0000000000000001ULL # define ISA_MAGIC_VALUE 0x0000000000000001ULL struct { uintptr_t indexed : 1; uintptr_t has_assoc : 1; uintptr_t has_cxx_dtor : 1; uintptr_t shiftcls : 44; // MACH_VM_MAX_ADDRESS 0x7fffffe00000 uintptr_t weakly_referenced : 1; uintptr_t deallocating : 1; uintptr_t has_sidetable_rc : 1; uintptr_t extra_rc : 14; # define RC_ONE (1ULL<<50) # define RC_HALF (1ULL<<13) }; # else // Available bits in isa field are architecture-specific. # error unknown architecture # endif // SUPPORT_NONPOINTER_ISA #endif }; |
SUPPORT_NONPOINTER_ISA
用于标记是否支持优化的 isa
指针,其字面含义意思是 isa
的内容不再是类的指针了,而是包含了更多信息,比如引用计数,析构状态,被其他 weak 变量引用情况。判断方法也是根据设备类型:
1 2 3 4 5 6 |
// Define SUPPORT_NONPOINTER_ISA=1 to enable extra data in the isa field. #if !__LP64__ || TARGET_OS_WIN32 || TARGET_IPHONE_SIMULATOR || __x86_64__ # define SUPPORT_NONPOINTER_ISA 0 #else # define SUPPORT_NONPOINTER_ISA 1 #endif |
综合看来目前只有 arm64 架构的设备支持,下面列出了 isa
指针中变量对应的含义:
变量名 | 含义 | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
indexed | 0 表示普通的 isa 指针的一部分空间来存储它的引用计数;否则 Runtime 会使用一张散列表来管理引用计数。
其实还有一种情况会改变引用计数的存储策略,那就是是否使用垃圾回收(用 TaggedPointer判断当前对象是否在使用 TaggedPointer 是看标志位是否为 1 :
isa 指针(NONPOINTER_ISA)用 64 bit 存储一个内存地址显然是种浪费,毕竟很少有那么大内存的设备。于是可以优化存储方案,用一部分额外空间存储其他内容。
综合看来目前只有 arm64 架构的设备支持,下面列出了
|