如上圖,內存佈局共分爲以下幾個區:c++
內核
:由系統控制處理的,大概有佔有1個GB棧
:函數、方法、局部變量等會儲存在這裏面堆
:經過alloc分配對象、block copy...bss
:未初始化的全局變量、靜態變量...data
:已初始化的全局變量、靜態變量...text
: 程序代碼保留
:由系統控制處理(0xC0000000 = 3221225472 = 3GB),因此從棧區到保留區佔有3GB 棧區從高地址向低地址延伸,堆區從低地址向高地址攀升 bss和data區在不區分是否初始化時,通常統稱全局區 棧區內存地址:⼀般爲:0x7開頭 堆區內存地址:⼀般爲:0x6開頭 數據段,BSS內存地址:⼀般爲:0x1開頭面試
iOS提供三種內存管理方案,TaggedPointer
,NONPOINTER_ISA
,散列表
.算法
malloc/free
###位運算知識補充數組
1010 1101 a
^ 0000 1100 b
---------
1010 0001
^ 0000 1100 b
---------
1010 1101 a
複製代碼
~100001
-------
011110
複製代碼
10000111 << 3 = 10000111000
10000111 >> 3 = 10000
複製代碼
1000 1100 a
| 1010 1010 b
------------
1010 1110
& 1010 1010 b
-------------
1010 1010 b
複製代碼
TaggedPointer
生成:#if __has_feature(objc_fixed_enum) || __cplusplus >= 201103L
enum objc_tag_index_t : uint16_t
#else
typedef uint16_t objc_tag_index_t;
enum
#endif
{
// 60-bit payloads
OBJC_TAG_NSAtom = 0,
OBJC_TAG_1 = 1,
OBJC_TAG_NSString = 2,
OBJC_TAG_NSNumber = 3,
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6,
// 60-bit reserved
OBJC_TAG_RESERVED_7 = 7,
// 52-bit payloads
OBJC_TAG_Photos_1 = 8,
OBJC_TAG_Photos_2 = 9,
OBJC_TAG_Photos_3 = 10,
OBJC_TAG_Photos_4 = 11,
OBJC_TAG_XPC_1 = 12,
OBJC_TAG_XPC_2 = 13,
OBJC_TAG_XPC_3 = 14,
OBJC_TAG_XPC_4 = 15,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
};
#if __has_feature(objc_fixed_enum) && !defined(__cplusplus)
typedef enum objc_tag_index_t objc_tag_index_t;
#endif
static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{
// PAYLOAD_LSHIFT and PAYLOAD_RSHIFT are the payload extraction shifts.
// They are reversed here for payload insertion.
// assert(_objc_taggedPointersEnabled());
if (tag <= OBJC_TAG_Last60BitPayload) {
// assert(((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT) == value);
uintptr_t result =
(_OBJC_TAG_MASK |
((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) |
((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
return _objc_encodeTaggedPointer(result);
} else {
// assert(tag >= OBJC_TAG_First52BitPayload);
// assert(tag <= OBJC_TAG_Last52BitPayload);
// assert(((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT) == value);
uintptr_t result =
(_OBJC_TAG_EXT_MASK |
((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
return _objc_encodeTaggedPointer(result);
}
}
複製代碼
源碼中經過對類型tag
和value
進行一些列位運算 tag << _OBJC_TAG_INDEX_SHIFT
說明最後一位是用來存儲類型, (value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT)
存儲value
, _OBJC_TAG_MASK
用來快速標記這是一個TaggedPointer
類型 而後調用_objc_encodeTaggedPointer
進行混淆,這也是爲何直接打印地址沒法看出這是一個特殊地址的緣由。安全
_objc_encodeTaggedPointer
和_objc_decodeTaggedPointer
使用的就是a^b^b=a
這個原理.static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
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; } } 複製代碼
在sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0
說明在這以前的版本objc_debug_taggedpointer_obfuscator
爲0,能夠直接看出地址的特殊性。單隻以後的版本就沒法看出了,須要手動_objc_decodeTaggedPointer
才能看到.bash
TaggedPointer
類型static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
複製代碼
經過位運算補充中的(4)a|b&b=b
可快速判斷是否爲TaggedPointer
數據結構
TaggedPointer
取值static inline uintptr_t
_objc_getTaggedPointerValue(const void * _Nullable ptr)
{
// assert(_objc_isTaggedPointer(ptr));
uintptr_t value = _objc_decodeTaggedPointer(ptr);
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
if (basicTag == _OBJC_TAG_INDEX_MASK) {
return (value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;
} else {
return (value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT;
}
}
複製代碼
首先進行_objc_decodeTaggedPointer
解密 而後使用和TaggedPointer
生成算法相反方式取出值.多線程
extern uintptr_t objc_debug_taggedpointer_obfuscator;
int a = 10;
NSString * t = [NSString stringWithFormat:@"jensen"];
NSNumber *aNum = @(a);// 64
NSLog(@"%s %p %@ 0x%lx",object_getClassName(aNum),aNum,aNum,_objc_encodeTaggedPointer(aNum));
NSLog(@"%s %p %@ 0x%lx",object_getClassName(t),t,t,_objc_encodeTaggedPointer(t));
uintptr_t _objc_encodeTaggedPointer(uintptr_t ptr)
{
return (objc_debug_taggedpointer_obfuscator ^ ptr);
}
複製代碼
打印結果:架構
__NSCFNumber 0xa39a2c1af54f3585 10 0xb0000000000000a3
NSTaggedPointerString 0xb39cca4dc3a96380 jensen 0xa006e65736e656a6
複製代碼
TaggedPointer
是經過對值和類型進行一系列位運算生成數值。經過這個數據能夠快速判斷類型,和獲取對應的值。對小類型(NSNumber,NSDate等)將不須要在使用64位來存儲,大大節省佔用的內存,提升建立和訪問效率。併發
- (void)taggedPointer_1 {
dispatch_queue_t queue = dispatch_queue_create("jensen", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(queue, ^{
self.nameStr = [NSString stringWithFormat:@"jensen"];
NSLog(@"%@",self.nameStr);
});
}
}
- (void)taggedPointer_2 {
dispatch_queue_t queue = dispatch_queue_create("jensen2", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(queue, ^{
self.nameStr = [NSString stringWithFormat:@"你們一塊兒搞起來"];
NSLog(@"%@",self.nameStr);
});
}
}
複製代碼
測試結果:taggedPointer_1
運行正常,taggedPointer_2
卻崩潰,什麼緣由?
從崩潰信息中,咱們知道是釋放過分致使的。 代碼中self.nameStr = [NSString stringWithFormat:@"你們一塊兒搞起來"];
,調用屬性的set
方法。
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
複製代碼
從上述代碼,咱們知道對象賦值(set)其實是retain/copy新值,釋放(release)舊值。因爲多線程操做不斷的retain/release,這種狀況下是不安全的。會形成對象過分釋放的狀況。
__attribute__((aligned(16), flatten, noinline))
id
objc_retain(id obj) {
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
__attribute__((aligned(16), flatten, noinline))
void
objc_release(id obj) {
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
複製代碼
若是是TaggedPointer
類型,在retain/release
會直接retuan
,不會真正的調用對象的retain/release
。當對象賦值爲jensen
屬於TaggedPointer
類型,當字符串中包含有中文,或者長度比較長,TaggedPointer
沒法存儲,那就不是TaggedPointer
了。
什麼是NONPOINTER_ISA
?
咱們知道在OC中,萬物皆對象objc_object
。
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
複製代碼
在此以前,我一直認爲isa
就是僅僅只是一個指針,實例對象的isa指向類,類對象的指針指向元類。但其實isa
除包含指針外還包含其餘信息,例如對象的引用計數、是否包含C++析構、是否被弱引用等等...這時這個isa就是NONPOINTER_ISA。isa是isa_t類型的聯合體,其內部經過位域技術儲存不少了對象的信息。
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
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)
};
}
複製代碼
nonpointer
:表示是否對 isa 指針開啓指針優化 0:純isa指針,1:不⽌是類對象地址,isa 中包含了類信息、對象的引⽤計數等has_assoc
:關聯對象標誌位,0沒有,1存在has_cxx_dtor
:該對象是否有 C++ 或者 Objc 的析構器,若是有析構函數,則須要作析構邏輯, 若是沒有,則能夠更快的釋放對象shiftcls
: 存儲類指針的值。開啓指針優化的狀況下,在 arm64 架構中有 33 位⽤來存儲類指針。magic
:⽤於調試器判斷當前對象是真的對象仍是沒有初始化的空間weakly_referenced
:標識對象是否被指向或者曾經指向⼀個 ARC 的弱變量, 沒有弱引⽤的對象能夠更快釋放。deallocating
:標誌對象是否正在釋放內存has_sidetable_rc
:當對象引⽤技術⼤於 10 時,則須要借⽤該變量存儲進位extra_rc
:表示該對象的引⽤計數值,其實是引⽤計數值減 1, 例如,若是對象的引⽤計數爲 10,那麼 extra_rc 爲 9。若是引⽤計數⼤於 10, 則須要使⽤到下⾯的 has_sidetable_rc。注:當對象重寫過retain,release,allocWithZone(rr/awz)
,那就再也不是一個NONPOINTER_ISA
SideTables
是系統維護的哈希表,內部存儲了一張張散列表SideTable
.每一張散列表主要用來記錄對象的引用計數,弱引用對象存儲等。
SideTables
數據結構:
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];
}
// Shortcuts for StripedMaps of locks.
void lockAll() {
for (unsigned int i = 0; i < StripeCount; i++) {
array[i].value.lock();
}
}
void unlockAll() {
for (unsigned int i = 0; i < StripeCount; i++) {
array[i].value.unlock();
}
}
void forceResetAll() {
for (unsigned int i = 0; i < StripeCount; i++) {
array[i].value.forceReset();
}
}
void defineLockOrder() {
for (unsigned int i = 1; i < StripeCount; i++) {
lockdebug_lock_precedes_lock(&array[i-1].value, &array[i].value);
}
}
void precedeLock(const void *newlock) {
// assumes defineLockOrder is also called
lockdebug_lock_precedes_lock(&array[StripeCount-1].value, newlock);
}
void succeedLock(const void *oldlock) {
// assumes defineLockOrder is also called
lockdebug_lock_precedes_lock(oldlock, &array[0].value);
}
const void *getLock(int i) {
if (i < StripeCount) return &array[i].value;
else return nil;
}
#if DEBUG
StripedMap() {
// Verify alignment expectations.
uintptr_t base = (uintptr_t)&array[0].value;
uintptr_t delta = (uintptr_t)&array[1].value - base;
assert(delta % CacheLineSize == 0);
assert(base % CacheLineSize == 0);
}
#else
constexpr StripedMap() {}
#endif
};
複製代碼
static unsigned int indexForPointer(const void *p)
對象指針經過哈希算法計算出對應的下標序號。T& operator[] (const void *p)
重寫[]
操做符,可經過,&SideTables()[oldObj]
方式獲取這個對象指針對應的SideTable
。lldb
調試,在SideTables
結構中獲取一張SideTable
(lldb) p indexForPointer(p)
(unsigned int) $4 = 4
(lldb) p array[indexForPointer(p)].value
((anonymous namespace)::SideTable) $5 = {
slock = {
mLock = (_os_unfair_lock_opaque = 0)
}
refcnts = {
Buckets = 0x0000000000000000
NumEntries = 0
NumTombstones = 0
NumBuckets = 0
}
weak_table = {
weak_entries = 0x0000000000000000
num_entries = 0
mask = 0
max_hash_displacement = 0
}
}
複製代碼
SideTable
內部數據結構:
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
...
};
複製代碼
spinlock_t slock
自旋鎖,用於控制SideTable
的訪問安全. refcnts
引用計數表,是一個Map
,用於存儲引用計數,具體下面會展開講解。 weak_table
弱引用表.
1.SidleTables
是一張哈希表,內部存了多張散列表。爲何須要使用多張? 答:對SidleTable
操做時,須要進行加鎖、解鎖。頻繁操做,會下降性能。多張表能夠分開加鎖,提升效率。 2.爲何不是一個類對應一個SidleTable
? 建立SidleTable
和管理SidleTable
都須要耗費性能,因此幾個類共用一個SidleTable
1.alloc
出來的引用技術是多少? 2.對象在何時會調用Dealloc
? 3.引用計數在何時會加,減? 4.引用計數存在哪? 5.dealloc底層,應該作一些什麼事情?
帶着上面幾個問題,咱們展開對源碼的分析。引用計數的核心就是對象的retain
、release
,所以首先從這2個函數入手分析:
-(id) retain
{
return _objc_rootRetain(self);
}
id
_objc_rootRetain(id obj)
{
assert(obj);
return obj->rootRetain();
}
objc_object::rootRetain()
{
return rootRetain(false, false);
}
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
//1. isTaggedPointer 直接返回
if (isTaggedPointer()) return (id)this;
//2.用於標記鎖的狀態
bool sideTableLocked = false;
//3.標記是否須要裝到到
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//4.不是nonpointer
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
//5.不是nonpointer類型,跳轉nonpointer
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides //6.析構,返回nil if (slowpath(tryRetain && newisa.deallocating)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); return nil; } //7.進位標記 uintptr_t carry; //8.extra_rc++ newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ if (slowpath(carry)) { //9. 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; //10.溢出時extra_rc保存一把 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; } id objc_object::sidetable_retain() { #if SUPPORT_NONPOINTER_ISA assert(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; table.lock(); size_t& refcntStorage = table.refcnts[this]; if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { refcntStorage += SIDE_TABLE_RC_ONE; } table.unlock(); return (id)this; } 複製代碼
TaggedPointer
類型,直接return
nonpointer
類型,調用sidetable_retain
,對引用計數表數值+1nonpointer
類型,extra_rc++
,判斷是否溢出,溢出時,extra_rc存儲RC_HALF(RC_HALF)
的引用計數,另外一半存儲值散列表的引用技術表。和retain
相似,此處就再也不貼源碼.
TaggedPointer
類型,直接return
nonpointer
類型,調用sidetable_retain
,對引用計數表數值-1nonpointer
類型,extra_rc--
,判斷是否下溢出RC_HALF(RC_HALF)
引用計數.deallocating
爲true,併發送dealloc
消息.inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
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();
}
複製代碼
TaggedPointer
返回的是(uintptr_t)this
nonpointer
返回的是 1 + bits.extra_rc
,若是引用計數表有值,還須要加上引用計數表的存儲值nonpointer
,返回計數表的存儲值if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
複製代碼
isa.nonpointer && !isa.weakly_referenced && !isa.has_assoc && !isa.has_cxx_dtor && !isa.has_sidetable_rc
,直接釋放總結:經過對retain
,release
,retainCount
,dealloc
源碼分析,上述5個問題都可以在裏面找到答案。此處就不在贅述。
1.弱引用對象是如何加入弱引用計數? 2.對象析構時,對象弱引用表中的對象如何設置爲nil?
NSObject * n = [[NSObject alloc] init];
__weak NSObject *weakN = n;
複製代碼
lldb調試得出,聲明覺得
weak
變量首先會執行
objc_initWeak
函數,所以咱們今後處入手進行分析。
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
複製代碼
newObj
不存在,直接return,不然調用storeWeak
static id
storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
if (haveOld && *location != oldObj) {
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
goto retry;
}
// Prevent a deadlock between the weak reference machinery
// and the +initialize machinery by ensuring that no
// weakly-referenced object has an un-+initialized isa.
if (haveNew && newObj) {
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
class_initialize(cls, (id)newObj);
// If this class is finished with +initialize then we're good. // If this class is still running +initialize on this thread // (i.e. +initialize called storeWeak on an instance of itself) // then we may proceed but it will appear initializing and // not yet initialized to the check above. // Instead set previouslyInitializedClass to recognize it on retry. previouslyInitializedClass = cls; goto retry; } } // Clean up old value, if any. if (haveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } // Assign new value, if any. if (haveNew) { newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating); // weak_register_no_lock returns nil if weak store should be rejected // Set is-weakly-referenced bit in refcount table. if (newObj && !newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); } // Do not set *location anywhere else. That would introduce a race. *location = (id)newObj; } else { // No new value. The storage is not changed. } SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); return (id)newObj; } 複製代碼
weak_unregister_no_lock
處理。void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
if (!referent) return;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer(entry, referrer);
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
複製代碼
weak_entry_for_referent
從waek_table
中獲取entry
remove_referrer
,在entry
的referrers
中找到地址的索引,entry->referrers[index] = nil;entry->num_refs--;
設置爲nil,並將num_refs
減1entry
是否還有值,沒有就在weak_table
移除這個entry
weak_register_no_lock
// now remember it and where it is being stored
weak_entry_t *entry;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);
weak_entry_insert(weak_table, &new_entry);
複製代碼
(entry = weak_entry_for_referent(weak_table, referent))
獲取entry
(1)entry
存在,調用append_referrer
,將new_referrer
添加到entry->referrers
將new_referrer
先賦值到entry->inline_referrers[i]
而後將entry->inline_referrers
循環對應拷貝到new_referrers
將new_referrers
賦值給entry->referrers = new_referrers;
(2)entry
不存在, 建立⼀個weak_entry_t
把referent
加⼊到weak_entry_t
的數組inline_referrers
,`` 把weak_table
擴容,weak_grow_maybe(weak_table)
把new_entry
加⼊到weak_table
中.weak_entry_insert(weak_table, &new_entry);
在三.引用計數的dealloc中,咱們知道,對象在析構(deealloc)時,若是存在弱引用對象:
...
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
//在引用技術標中,移除這個對象。
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock()
...
複製代碼
存在弱引用對象,調用weak_clear_no_lock
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc //printf("XXX no entry for clear deallocating %p\n", referent); return; } // zero out references weak_referrer_t *referrers; size_t count; if (entry->out_of_line()) { referrers = entry->referrers; count = TABLE_SIZE(entry); } else { referrers = entry->inline_referrers; count = WEAK_INLINE_COUNT; } for (size_t i = 0; i < count; ++i) { objc_object **referrer = referrers[i]; if (referrer) { if (*referrer == referent) { *referrer = nil; } else if (*referrer) { _objc_inform("__weak variable at %p holds %p instead of %p. " "This is probably incorrect use of " "objc_storeWeak() and objc_loadWeak(). " "Break on objc_weak_error to debug.\n", referrer, (void*)*referrer, (void*)referent); objc_weak_error(); } } } weak_entry_remove(weak_table, entry); } 複製代碼
weak_table
獲取對象的entry
entry
下的referrers
,將其指向設置爲nil,*referrer = nil;
weak_table
中移除entry
變量修飾符有一下幾種狀況:
typedef enum {
objc_ivar_memoryUnknown, // unknown / unknown
objc_ivar_memoryStrong, // direct access / objc_storeStrong
objc_ivar_memoryWeak, // objc_loadWeak[Retained] / objc_storeWeak
objc_ivar_memoryUnretained // direct access / direct access
} objc_ivar_memory_management_t;
複製代碼
經過源碼分析變量不一樣修飾符的setter
方法的處理:
void _object_setIvar(id obj, Ivar ivar, id value, bool assumeStrong)
{
if (!obj || !ivar || obj->isTaggedPointer()) return;
ptrdiff_t offset;
objc_ivar_memory_management_t memoryManagement;
_class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement);
if (memoryManagement == objc_ivar_memoryUnknown) {
if (assumeStrong) memoryManagement = objc_ivar_memoryStrong;
else memoryManagement = objc_ivar_memoryUnretained;
}
id *location = (id *)((char *)obj + offset);
switch (memoryManagement) {
case objc_ivar_memoryWeak: objc_storeWeak(location, value); break;
case objc_ivar_memoryStrong: objc_storeStrong(location, value); break;
case objc_ivar_memoryUnretained: *location = value; break;
case objc_ivar_memoryUnknown: _objc_fatal("impossible");
}
}
複製代碼
TaggedPointer
類型,直接return
objc_ivar_memory_management_t
._class_lookUpIvar(obj->ISA(), ivar, offset, memoryManagement)
(1)objc_ivar_memoryWeak
,調用objc_storeWeak
操做弱引用表,上述已經分析過. (2)objc_ivar_memoryStrong
,調用objc_storeStrong
,retain新值,釋放舊值 (3)objc_ivar_memoryUnretained
,直接將value存儲至*location
。這也說明爲何Unretained
是不安全的。AutoReleasePool
是ARC
引入的,用於管理對象的引用計數。 如下是AutoReleasePool
的幾個要點:
AutoReleasePool
結構圖:
AutoReleasePool
數據結構:
class AutoreleasePoolPage;
struct AutoreleasePoolPageData {
magic_t const magic; // 16
__unsafe_unretained id *next; //8
pthread_t const thread; // 8
//證實了雙向鏈表結構
AutoreleasePoolPage * const parent; //8
AutoreleasePoolPage *child; //8
uint32_t const depth; // 4
uint32_t hiwat; // 4
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
複製代碼
AutoreleasePoolPage
是個繼承於AutoreleasePoolPageData
結構體的類,objc4-779.1
版本開始獨立出AutoreleasePoolPageData
結構體,以前變量是直接在AutoreleasePoolPage
中。magic_t const magic
:用來校驗AutoreleasePoolPage
的結構是否完整__unsafe_unretained id *next
: 指向最新添加的autorelease對象的下一個位置,初始化時指向begin()pthread_t const thread
:當前線程AutoreleasePoolPage * const parent
:指向父節點,第一個parent節點爲nilAutoreleasePoolPage *child
:指向子節點,最後一個child節點爲niluint32_t const depth
:表明深度,從0開始,遞增+1uint32_t hiwat
:表明 high water Mark 最大入棧數量標記使用clang -rewrite-objc main.m -o main.cpp
編譯以下代碼:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Jensen");
}
return 0;
}
複製代碼
編譯結果:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_v7_6tlrq64x5w5gqg17582f4p500000gn_T_main_3f39be_mi_0);
}
return 0;
}
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
複製代碼
@autoreleasepool{}
其實是實例化__AtAutoreleasePool
,在構造方法中調用objc_autoreleasePoolPush
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
複製代碼
OBJC_DEBUG_POOL_ALLOCATION
判斷自動釋放池是否被容許跟蹤調試,若是容許調用autoreleaseNewPage
,不然進入autoreleaseFast
.此處,咱們分析autoreleaseFast
。objc_autoreleasePoolPush
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
複製代碼
AutoreleasePoolPage
的hotPage
hotPage
,而且未滿,直接調用page->add(obj)
將對象添加到AutoreleasePoolPage
hotPage
,可是已滿,調用autoreleaseFullPage
hotPage
,說明是第一次加入,調用autoreleaseNoPage
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
複製代碼
將對象加入到hotPage
中.
static __attribute__((noinline))
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
// The hot page is full.
// Step to the next non-full page, adding a new page if necessary.
// Then add the object to that page.
ASSERT(page == hotPage());
ASSERT(page->full() || DebugPoolAllocation);
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
}
複製代碼
循環找到最後一頁,當前page做爲父page建立一個新的AutoreleasePoolPage
,將新建立的page設置爲hotPage
,調用add
將對象加入到新page
中.
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
ASSERT(!hotPage());
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
// We are pushing a second pool over the empty placeholder pool
// or pushing the first object into the empty placeholder pool.
// Before doing that, push a pool boundary on behalf of the pool
// that is currently represented by the empty placeholder.
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
objc_thread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder();
}
// We are pushing an object or a non-placeholder'd pool. // Install the first page. AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); setHotPage(page); // Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
}
複製代碼
會直接建立第一個page
,並將這個page
設置爲hotPage
,而後加入邊界符POOL_BOUNDARY
用_objc_autoreleasePoolPrint();
打印一個空的自動釋放池:
一張page佔用4096
字節,從圖中咱們知道page屬性佔用56(3 * 16 + 8)
字節,一個page能容納505((4096 - 56)/8 = 505)
個對象,第一頁包含POOL
的特殊邊界符,佔用1
個對象,所以第一頁能容納504個
對象和1
個特殊標記符,其餘頁面能容納505
個對象。
void
_objc_autoreleasePoolPop(void *ctxt)
{
objc_autoreleasePoolPop(ctxt);
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
page = coldPage();
token = page->begin();
} else {
page = pageForPointer(token);
}
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
return popPage<false>(token, page, stop);
}
複製代碼
_objc_autoreleasePoolPop
token
指定須要釋放到的位置token
對應的pagepopPage<false>(token, page, stop);
開始poptemplate<bool allowDebug>
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// memory: delete empty children
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
} else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
複製代碼
page->releaseUntil(stop);
釋放對象void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage while (this->next != stop) { // Restart from hotPage() every time, in case -release // autoreleased more objects AutoreleasePoolPage *page = hotPage(); // fixme I think this `while` can be `if`, but I can't prove it
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
ASSERT(page->empty());
}
#endif
}
複製代碼
總結: 當要pop
對象的時候,系統給一個token
對象指針,這個指針用於指定釋放的程度 找到token
對象所在的page
,並生成一個stop
中止對象,而後開始pop
操做 page->releaseUntil(stop)
,內部循環遍歷執行對象的release
,直到stop
對象,並將當前page
設爲hotpage
將已經釋放對象所屬的page殺了,即刪除空的child page.
前面已經介紹了objc_autoreleasePoolPush
和objc_autoreleasePoolPop
,接下來咱們看看autorelease
又作了什麼.
static inline id autorelease(id obj)
{
ASSERT(obj);
ASSERT(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
複製代碼
autorelease
的實現和objc_autoreleasePoolPush
相似,這裏就不在贅述了。
App啓動後,蘋果在主線程 RunLoop
裏註冊了兩個 Observer
,其回調都是 _wrapRunLoopWithAutoreleasePoolHandler()
。
第一個 Observer
監視的事件是 Entry(即將進入Loop),其回調內會調用 _objc_autoreleasePoolPush()
建立自動釋放池。其 order 是-2147483647,優先級最高,保證建立釋放池發生在其餘全部回調以前。
第二個 Observer
監視了兩個事件: BeforeWaiting(準備進入休眠)
時調用_objc_autoreleasePoolPop()
和 _objc_autoreleasePoolPush()
釋放舊的池並建立新池;Exit(即將退出Loop)
時調用_objc_autoreleasePoolPop()
來釋放自動釋放池。這個 Observer 的 order 是 2147483647,優先級最低,保證其釋放池子發生在其餘全部回調以後。
在主線程執行的代碼,一般是寫在諸如事件回調、Timer回調內的。這些回調會被 RunLoop 建立好的 AutoreleasePool 環繞着,因此不會出現內存泄漏,開發者也沒必要顯示建立 Pool了。
一個線程只有一個autoreleasePool
autoreleasePool
嵌套時,只會建立一個page
,可是有兩個池邊界
observers = (
"<CFRunLoopObserver 0x600001238280 [0x10b19ab68]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10dd891b1), context = <CFArray 0x600002d3c1b0 [0x10b19ab68]>{type = mutable-small, count = 0, values = ()}}",
"<CFRunLoopObserver 0x60000123c500 [0x10b19ab68]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x10d95b473), context = <CFRunLoopObserver context 0x60000083cfc0>}",
"<CFRunLoopObserver 0x600001238140 [0x10b19ab68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x10ddb8dfc), context = <CFRunLoopObserver context 0x7fdae6d020c0>}",
"<CFRunLoopObserver 0x6000012381e0 [0x10b19ab68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x10ddb8e75), context = <CFRunLoopObserver context 0x7fdae6d020c0>}",
"<CFRunLoopObserver 0x600001238320 [0x10b19ab68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10dd891b1), context = <CFArray 0x600002d3c1b0 [0x10b19ab68]>{type = mutable-small, count = 0, values = ()}}"
),
複製代碼