如今咱们使用oc编程不用进行手动内存管理得益于ARC机制。ARC帮咱们免去了大部分对对象的内存管理操做,其实ARC只是帮咱们在合适的地方或者时间对对象进行-retain
或-release
,并非不用进行内存管理。c++
经过我以前分析的oc对象内存结构能够知道,其实对象的引用计数是存放在对象的isa
指针中,isa
在OBJC2
中是一个通过优化的指针不单存放着类对象的地址还存放着其余有用的信息,其中就包括引用计数信息的存储。 isa_t
的结构位域中有两个成员与引用计数有关分别是编程
uintptr_t has_sidetable_rc : 1; //isa_t指针第56位开始占1位
uintptr_t extra_rc : 8 //isa_t指针第57位开始占8位
复制代码
extra_rc
存放的是多是对象部分或所有引用计数值减1。数组
has_sidetable_rc
为一个标志位,值为1时表明 extra_rc
的8位内存已经不能存放下对象的retainCount
, 须要把一部分retainCount
存放地另外的地方。安全
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
//isTaggedPointer直接返回指针
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
//标识是否须要去查找对应的SideTable
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//这里是isa没有通过指针位域优化的状况,直接进入全局变量中找出对应的SideTable类型值操做retainCount
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
//是否溢出的标识 , 若是调用addc函数后 isa的extra_rc++后溢出的话carry会变成非零值
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed extra_rc 溢出了
if (!handleOverflow) {
ClearExclusive(&isa.bits);
//这里是从新调用rootRetain参数handleOverflow = true
return rootRetain_overflow(tryRetain);
}
//执行到这里表明extra_rc已经移除了,须要把 extra_rc 减半 ,把那一半存放到对应的SideTable类型值中
// 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;
}
复制代码
rootRetain
主要是处理isa
中extra_rc
中加法操做: 在extra_rc ++
没有溢出的状况下不用特殊处理,若是溢出的话把extra_rc
一半的值减掉,把减掉的值存到一个SideTable
类型的变量中。bash
struct SideTable {
spinlock_t slock; //操做内部数据的锁,保证线程安全
RefcountMap refcnts;//哈希表[假装的对象指针 : 64位的retainCoint信息值]
weak_table_t weak_table;//存放对象弱引用指针的结构体
}
复制代码
SideTabel
实际上是一个包装了3个成员变量的结构体上面已注释各成员的做用,而RefcountMap refcnts
这个成员就是咱们稍后重点要分析的存放对象额外retainCount
的成员变量。ide
获取objc_object
对应的SideTable
类型变量函数
alignas(StripedMap<SideTable>) static uint8_t
SideTableBuf[sizeof(StripedMap<SideTable>)];
SideTable& table = SideTables()[this];
//函数SideTables() 实现
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
复制代码
能够看出全部对象对应的SideTable
。都存储在一个全局变量SideTableBuf
中,把SideTableBuf
定义成字符数组其目的是为了方便计算StripedMap<SideTable>
的内存大小,从而开辟一块与StripedMap<SideTable>
大小相同的内存。其实能够把 SideTableBuf
当作一个全局的StripedMap<SideTable>
类型的变量,由于SideTables()
方法已经把返回值SideTableBuf
强转成StripedMap<SideTable>
类型的变量。下面分析下StripedMap
这个类工具
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
struct PaddedT {
T value alignas(CacheLineSize);
};
public:
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
}
复制代码
从上面定义能够看出StripedMap<SideTable>
类实际上是包装了一个结构体的成员变量array
的哈希表,该成员变量是一个装着PaddedT
类型的数组,PaddedT
这个结构构体实际上就是咱们模板类传进的SideTable
。所以这里能够把array
当作是一个装着SideTable
的容器,容量为8或64(运行的平台不一样而不一样)。源码分析
当系统调用 SideTables()[对象指针]
时,StripedMap<SideTable>
这个哈希表就会在array
中找出对应数组指针的SideTable
类返回,这里能够看出其中的一个SideTable
类变量可能对应多个不一样的对象指针。post
if (slowpath(transcribeToSideTable)) {
sidetable_addExtraRC_nolock(RC_HALF);
}
复制代码
执行到下面的if语句里面的 sidetable_addExtraRC_nolock(RC_HALF);
表明通过do
语句的执行逻辑得出extra_rc
已经溢出了,接下来看下溢出处理的实现
// Move some retain counts to the side table from the isa field.
// Returns true if the object is now pinned.
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this];
size_t& refcntStorage = table.refcnts[this];
size_t oldRefcnt = refcntStorage;
// isa-side bits should not be set here
assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
//已经溢出了 直接返回true
if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
//把 delta_rc 左已两位后与 oldRefcnt 相加 判断是否有溢出
uintptr_t carry;
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
if (carry) {
// 溢出处理
// SIDE_TABLE_FLAG_MASK = 0b11 = SIDE_TABLE_DEALLOCATING + SIDE_TABLE_WEAKLY_REFERENCED
// SIDE_TABLE_RC_PINNED 溢出标志位
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
return true;
}
else { //没有溢出
refcntStorage = newRefcnt;
return false;
}
}
复制代码
能够看出extra_c
溢出的时候是把一半值减掉后存进对应对象指针的SideTable
的成员变量RefcountMap refcnts
中。在弄清楚上面代码逻辑前,先看下几个重要的宏定义
// The order of these bits is important.
#define SIDE_TABLE_WEAKLY_REFERENCED (1UL<<0)
#define SIDE_TABLE_DEALLOCATING (1UL<<1) // MSB-ward of weak bit
#define SIDE_TABLE_RC_ONE (1UL<<2) // MSB-ward of deallocating bit
#define SIDE_TABLE_RC_PINNED (1UL<<(WORD_BITS-1))
复制代码
经过宏定义及RefcountMap
的实现(下面会分析)能够发现refcntStorage
实际上是一个8字节(64位)大小的内存其内存结构及对应的标识位以下图
根据上面的代码用this
指针获取存放在SideTable
内部引用计数refcntStorage
后,会分别判断这3个标识位都为0时才执行计数增长的操做,在调用addc
是也会执行 delta_rc << SIDE_TABLE_RC_SHIFT
左移的操做来避开相应的标识位后在相应的内存位上。若是相加后溢出了,会把最高的移除标识位置为1。
通过sidetable_addExtraRC_nolock
处理后isa指针中的extrc_rc
在溢出的状况下成功吧一半的数值移存到了对应SideTable
的refcntStorage
哈希表中,从而释放了isa.extra_rc
的内存继续记录retainCount
。
咱们先看下存放extra_rc
溢出部分的RefcountMap
定义:
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;
复制代码
能够看出 RefcountMap
实际上是DenseMap
的模板类的别名, DenseMap
这是继承自DenseMapBase
的类,其内部实现能够看出DenseMap
实际上是一个典型的哈希表(相似oc的NSDictionary
),经过分析能够发现关于DenseMap
的几点
KeyT
用 DisguisedPtr<objc_object>
包装对象指针,此类是对对象指针值(obje_object *)的封装或说是假装,使其不收内存泄露测试工具的影响。ValueT
用size_t
代替,size_t
是一个64位内存的unsigned int
KeyInfoT
用DenseMapInfo<KeyT>
代替,在此处就至关于DenseMapInfo<DisguisedPtr<objc_object>
,DenseMapInfo
封装了比较重要的方法哈希值的获取用于查找对应Key的内容。主要为哈希表提供了KeyT的判等isEqual,以及KeyT类型值的hashValue的获取下面是代码实现
//Key判等实现,直接用 == 完成判等
static bool isEqual(const T *LHS, const T *RHS) { return LHS == RHS; }
//根据Key获取对应的hash值
static unsigned getHashValue(const T *PtrVal) {
//ptr_hash调用到下面的内联函数
return ptr_hash((uintptr_t)PtrVal);
}
#if __LP64__
static inline uint32_t ptr_hash(uint64_t key)
{
key ^= key >> 4;
key *= 0x8a970be7488fda55;
key ^= __builtin_bswap64(key);
return (uint32_t)key;
}
#else
static inline uint32_t ptr_hash(uint32_t key)
{
key ^= key >> 4;
key *= 0x5052acdb;
key ^= __builtin_bswap32(key);
return key;
}
#endif
复制代码
简化了源码看主要查找实现
//重写操做符[]
ValueT &operator[](const KeyT &Key) {
return FindAndConstruct(Key).second;
}
value_type& FindAndConstruct(const KeyT &Key) {
BucketT *TheBucket;
if (LookupBucketFor(Key, TheBucket))
return *TheBucket;
return *InsertIntoBucket(Key, ValueT(), TheBucket);
}
//查找实现
template<typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
//存放全部内容的bucket数组
const BucketT *BucketsPtr = getBuckets();
//bucket个数
const unsigned NumBuckets = getNumBuckets();
//没有内容直接返回
if (NumBuckets == 0) {
FoundBucket = 0;
return false;
}
//根据Val的哈希值算出的bucket的索引 getHashValue调用的是KeyInfo的实现
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
unsigned ProbeAmt = 1;
while (1) {
//从buckets数组拿出对应索引的值
const BucketT *ThisBucket = BucketsPtr + BucketNo;
if (KeyInfoT::isEqual(Val, ThisBucket->first)) { //符合 key == indexOfKey
//赋值外面传进来的参数
FoundBucket = ThisBucket;
return true;
}
BucketNo += ProbeAmt++;
BucketNo&= (NumBuckets-1);
}
}
复制代码
首先咱们看下主要处理release
逻辑的方法实现
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
复制代码
方法主要分为几大逻辑模块
extra_rc --
后位溢出的状况处理ectra_rc --
后下溢出的状况处理首先分析执行extra_rc--
后正常未下溢出的状况,此状况主要是经过subc
函数让newisa.bit
与RC_ONE(1ULL<<56)
相加,最后更新isa
的值。
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//计算溢出的标识位
uintptr_t carry;
// extra_rc-- RC_ONE -> (1<<56)在isa中恰好是extra_rc的开始位
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);
if (slowpath(carry)) {
goto underflow;//下溢出了, 直接跳转下溢出的处理逻辑
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits))); // 把newisa.bits 赋值给isa.bits ,并退出 while 循环
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
复制代码
加入通过subc
函数的运算newisa.bits
发生了下溢出的话,直接跳转到underflow
的处理逻辑中。下面分析下underflow
的主要逻辑
underflow:
newisa = oldisa;
if (slowpath(newisa.has_sidetable_rc)) { //用SideTabel的refcnts
//为对应的SideTable加锁后在操做器内存数据
if (!sideTableLocked) {
sidetable_lock();
sideTableLocked = true;
//修改下 sideTableLocked = true; 从新调用retry
goto retry;
}
// 把一部分的refCount出来赋值给 borrowed
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
if (borrowed > 0) {
//把引用计数 - 1 后赋值给 extra_rc
newisa.extra_rc = borrowed - 1;
//更新isa的extra_rc
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
//下面是处理更新isa值失败的重试操做
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);
}
}
}
//重试更新isa值仍是失败的话,把borrowed再次存进对象的SideTable中。再周一遍retry的代码逻辑(开始的do while位置)
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
//执行到这里表明成功把对应SideTable的值转移了部分值到isa.ectra_rc中,并为对应SideTable类型值加锁
sidetable_unlock();
return false;
}
else {
//来到else语句的话表明对应SideTable已经没有存储额外的retainCount。接下来要执行对象内存释放的逻辑了。
}
}
复制代码
经过上面下溢出处理的代码分析能够知道,extra_rc--
后发生下溢出的话,系统会优先去查找对象对应SideTable
值中存储的哈希表refcnts
变量,在经过refcnts
查找到对应对象存储的8字节内存的count
去一部分出来(大小为isa.extra_rc
恰好溢出的一半大小),存放到isa.extra_rc
中。若是此时refcnts
取出的值也为0了就表明对象能够释放掉内存了。
对象内存释放的调用,主要是把isa.deallocating
的标识位置为1,而后执行SEL_dealloc
释放对象内存。
// 上面若是 borrowed == 0 来到这里表明retainCount等于0 对象能够释放了
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();
__sync_synchronize();
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); //表明引用计数已经等于0 调用dealloc释放内存
}
return true;
复制代码
对象经过retain
与release
巧妙地使内部的isa.extra_rc
与外部存储在对应其自己的SideTable
类中存储的引用计数值增减有条不紊地进行着加减法。并经过判断当两个值都知足必定条件时就执行对象的SEL_dealloc
消息,释放内存