在OC底层探索(十八)内存五大区中了解了栈区、堆区、全局区、常量区和代码区五大区的分布,那么今天来分析一下内存管理的引用计数。
taggedpointer小对象类型
在iOS中NSNumber、NSString都是有小对象组成的对象,存放在常量区
,并且占用空间非常的小
。
在分析ISA、cache、read_image和sendMessage时都提及到了小对象类型。
分析
- 在
_read_images
中可以看到initializeTaggedPointerObfuscator
方法,当时并没有分析,这是一步是进行初始化小对象类型。
-
查看
initializeTaggedPointerObfuscator
- 在iOS14时,发现苹果将小对象类型的指针地址进行了混淆。
static void
initializeTaggedPointerObfuscator(void)
{
if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||
// Set the obfuscator to zero for apps linked against older SDKs,
// in case they're relying on the tagged pointer representation.
DisableTaggedPointerObfuscation) {
objc_debug_taggedpointer_obfuscator = 0;
} else {
// Pull random data into the variable, then shift away all non-payload bits.
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
}
}
- 在源码中发现了
_objc_decodeTaggedPointer
和_objc_taggedPointersEnabled
,分别是对小对象类型进行加密
与解密
,即执行异或操作
。
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
static inline bool
_objc_taggedPointersEnabled(void)
{
extern uintptr_t objc_debug_taggedpointer_mask;
return (objc_debug_taggedpointer_mask != 0);
}
- 分别写NSString和NSNumber变量,并进行解密查看其地址。
NSNumber *number1 = @1;
NSString *str2 = [NSString stringWithFormat:@"b"];
NSLog(@"%@-%p-%@",object_getClass(number1),number1,number1);
NSLog(@"%@-%p-%@ - 0x%lx",object_getClass(number1),number1,number1,_objc_decodeTaggedPointer_(number1));
NSLog(@"%p-%@",str2,str2);
NSLog(@"0x%lx",_objc_decodeTaggedPointer_(str2));
打印结果为:
_objc_isTaggedPointer
中是进行判断是否为小对象类型,地址直接是对_OBJC_TAG_MASK
进行与运算
进行判断,其中_OBJC_TAG_MASK
是1
左移
63
位,就是判断最高位
是否为1
,如果是1
那么就是小对象
类型。
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
define _OBJC_TAG_MASK (1UL<<63)
- 其中小对象的类型是由地址的高2~4位决定的,其中
010
=>2
代表的是NSString
,011
=>3
代表的是NSNumber
.
-
分析
0xb000000000000012
、0xa000000000000621
- 0xa =>
1011
最高位1
代表的是小对象
类型,011
代表的是NSNumber
类型 - 0xb =>
1010
最高位1
代表的是小对象
类型,010
代表的是NSString
类型
- 0xa =>
小结:
-
Tagged Pointer
专⻔用来存储小
的对象,例如NSNumber和NSDate -
Tagged Pointer
指针
的值
不再是地址
了,而是真正的值
。所以,实际上它不再 是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储 在堆中,所以retain
和release
没有
对小对象类型进行内存管理
,即可以自主回收
-
在
内存
读取上有着3
倍的效率,创建时
比以前快106
倍。
SiddeTables散列表
在NONPOINTER_ISA
中引用计数是存储在extra_rc
中,但是如果当这里面存满了,那么就向SiddeTables
中存储.
- 应用中存在多张散列表
- 每张散列表中都存放着
自旋锁
、引用计数
和弱引用表
.
源码分析
- SideTable是存在到SideTablesMap (
hash
表)中
static StripedMap<SideTable>& SideTables() {
return SideTablesMap.get();
}
-
在
StripedMap
中我们发现SideTable
会有多张,真机
最多有8
张散列表模拟器
最多有64
张散列表
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR//真机、非模拟器
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
struct PaddedT {
T value alignas(CacheLineSize);
};
PaddedT array[StripeCount];
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
public:
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast<StripedMap<T>>(this)[p];
}
......
};
SideTable
是一个结构体
,分别存放着spinlock_t
、RefcountMap
和`weak_table_t类型的属性。
struct SideTable {
spinlock_t slock;//自旋锁
RefcountMap refcnts;//引用计数
weak_table_t weak_table;//弱引用表
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
retain 引用计数加一
- 搜索
objc_retain
,发现在源码中,对小对象
类型进行了判断
,如果是小对象
类型就直接返回该对象
,没有
进行引用计数加一
。
__attribute__((aligned(16), flatten, noinline))
id
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
- 进入
retain
,查看rootRetain
inline id
objc_object::retain()
{
ASSERT(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}
- 查看
rootRetain
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//判断是否为nonpointerISA,
if (slowpath(!newisa.nonpointer)) {
//不是nonpointerISA直接操作散列表
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides
//判断是否正在析构
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
//newisa的bits 加一,其中carry
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
//如果isa中引用计数满了就操作散列表,直接将extra_rc的一半存储到散列表中
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
retain操作流程
-
判断是否为
nonpointerISA
-
不是
nonpointerISA
,直接操作散列表
-
是
nonpointerISA
,在进行判断是否当前对象是否正在释放
-
正在释放
:直接返回,不进行操作
-
没有在释放
,引用计数
加一
,存储到isa
的extra_rc
中,如果extra_rc
满了
,就将其中一半
存储到散列表
中。
-
-
流程图
realse 引用计数减一
-
通过
setProperty -> reallySetProperty -> objc_release -> release -> rootRelease -> rootRelease
顺序,进入rootRelease
源码,其操作与retain 相反
-
判断是否是
Nonpointer isa
,如果不是
,则直接对散列表
进行-1
操作 -
如果
是
Nonpointer isa
,则对extra_rc
中的引用计数值进行-1
操作,并存储此时的extra_rc
状态到carry
中 -
如果此时的状态
carray
为0
,则走到underflow
流程 -
underflow
流程有以下几步:-
判断
散列表
中是否
存储
了一半
的引用计数
-
如果
是
,则从散列表
中取出
存储的一半
引用计数,进行-1
操作,然后存储到extra_rc
中 -
如果此时
extra_rc
没有
值,散列表
中也是空
的,则直接进行析构
,即dealloc
操作,属于自动触发
-
-
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//判断是否是Nonpointer isa
if (slowpath(!newisa.nonpointer)) {
//如果不是,则直接操作散列表-1
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return false;
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
//进行引用计数-1操作,即extra_rc-1
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
//如果此时extra_rc的值为0了,则走到underflow
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
newisa = oldisa;
//判断散列表中是否存储了一半的引用计数
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
goto retry;
}
// Try to remove some retain counts from the side table.
//从散列表中取出存储的一半引用计数
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
// To avoid races, has_sidetable_rc must remain set
// even if the side table count is now zero.
if (borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
//进行-1操作,然后存储到extra_rc中
newisa.extra_rc = borrowed - 1; // redo the original decrement too
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
if (newisa2.nonpointer) {
uintptr_t overflow;
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
// Decrement successful after borrowing from side table.
// This decrement cannot be the deallocating decrement - the side
// table lock and has_sidetable_rc bit ensure that if everyone
// else tried to -release while we worked, the last one would block.
sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
//此时extra_rc中值为0,散列表中也是空的,则直接进行析构,即自动触发dealloc流程
// Really deallocate.
//触发dealloc的时机
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
//发送一个dealloc消息
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
retainCount
面试题:alloc创建的对象的引用计数为多少?
NSObject *objc = [NSObject alloc];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
打印结果是:
但是此时objc的引用计数为0
疑问:为什么objc的引用计数为0,但是RetainCount打印出来是1呢?
问题出在retainCount的实现中!!!
- 进入
retainCount
->_objc_rootRetainCount
->rootRetainCount
源码,其实现如下
- (NSUInteger)retainCount {
return _objc_rootRetainCount(self);
}
👇
uintptr_t
_objc_rootRetainCount(id obj)
{
ASSERT(obj);
return obj->rootRetainCount();
}
👇
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
//如果是nonpointer isa,才有引用计数的下层处理
if (bits.nonpointer) {
//alloc创建的对象引用计数为0,包括sideTable,所以对于alloc来说,是 0+1=1,这也是为什么通过retaincount获取的引用计数为1的原因
uintptr_t rc = 1 + bits.extra_rc;
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
//如果不是,则正常返回
sidetable_unlock();
return sidetable_retainCount();
}
-
由源码中可以看出来,retaincount是有
bits.extra_rc
+sidetable_getExtraRC_nolock
+1
得到。 -
综上所述,
alloc
创建的对象实际的引用计数为0
,其引用计数打印结果为1
,是因为在底层rootRetainCount
方法中,引用计数默认+1
了,但是这里只有对引用计数的读取
操作,是没有写入
操作的,简单来说就是:为了防止alloc创建的对象被释放(引用计数为0会被释放),所以在编译阶段,程序底层默认进行了+1操作。实际上在extra_rc中的引用计数仍然为0
小结
-
alloc
创建的对象没有
retain
和release
-
alloc
创建对象的引用计数为0
,会在编译时期
,程序默认加1
,所以读取引用计数时为1