Object-C內存管理

一.內存佈局

如上圖,內存佈局共分爲以下幾個區:c++

  • 內核:由系統控制處理的,大概有佔有1個GB
  • :函數、方法、局部變量等會儲存在這裏面
  • :經過alloc分配對象、block copy...
  • bss:未初始化的全局變量、靜態變量...
  • data:已初始化的全局變量、靜態變量...
  • text: 程序代碼
  • 保留:由系統控制處理

(0xC0000000 = 3221225472 = 3GB),因此從棧區到保留區佔有3GB 棧區從高地址向低地址延伸,堆區從低地址向高地址攀升 bss和data區在不區分是否初始化時,通常統稱全局區 棧區內存地址:⼀般爲:0x7開頭 堆區內存地址:⼀般爲:0x6開頭 數據段,BSS內存地址:⼀般爲:0x1開頭面試

二.內存管理方案

iOS提供三種內存管理方案,TaggedPointer,NONPOINTER_ISA,散列表.算法

1.TaggedPointer:

  • ⼩對象-NSNumber,NSDate等
  • 再也不是一個簡單的地址,而是真正的值,裏面包含值,類型等等。它再也不是一個對象,內存不存儲在堆中,也不須要malloc/free
  • 讀取速度快3倍,建立速度提高106倍。

###位運算知識補充數組

  • (1)對同一個數值異或(^)兩次,能回到原來的值(a^b^b=a)。
1010 1101  a
^ 0000 1100  b 
  ---------
  1010 0001
^ 0000 1100  b
  ---------
  1010 1101  a
複製代碼
  • (2)按位取反(~)
~100001
-------
 011110
複製代碼
  • (3)左移(<<)右移(>>)操做
10000111 << 3 = 10000111000
10000111 >> 3 = 10000
複製代碼
  • (4)位與(&)位或(|), (a | b ^ b = b)
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);
    }
}

複製代碼

源碼中經過對類型tagvalue進行一些列位運算 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了。

2.NONPOINTER_ISA:⾮指針型isa

什麼是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

3.散列表:引⽤計數表,弱引⽤表

SideTables是系統維護的哈希表,內部存儲了一張張散列表SideTable.每一張散列表主要用來記錄對象的引用計數,弱引用對象存儲等。

SideTables

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

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底層,應該作一些什麼事情?

帶着上面幾個問題,咱們展開對源碼的分析。引用計數的核心就是對象的retainrelease,所以首先從這2個函數入手分析:

retain

-(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; } 複製代碼
  • 1.TaggedPointer類型,直接return
  • 2.不是nonpointer類型,調用sidetable_retain,對引用計數表數值+1
  • 3.nonpointer類型,extra_rc++,判斷是否溢出,溢出時,extra_rc存儲RC_HALF(RC_HALF)的引用計數,另外一半存儲值散列表的引用技術表。

release

retain相似,此處就再也不貼源碼.

  • 1.TaggedPointer類型,直接return
  • 2.不是nonpointer類型,調用sidetable_retain,對引用計數表數值-1
  • 3.nonpointer類型,extra_rc--,判斷是否下溢出
  • 4.當下溢出時,判斷散列表是否還有值,若是有就從散列表借,extra_rc存儲RC_HALF(RC_HALF)引用計數.
  • 若是散列表也沒有了,那就標記deallocating爲true,併發送dealloc消息.

retainCount()

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();
}
複製代碼
  • 1.TaggedPointer返回的是(uintptr_t)this
    1. nonpointer返回的是 1 + bits.extra_rc,若是引用計數表有值,還須要加上引用計數表的存儲值
    1. nonpointer,返回計數表的存儲值

dealloc

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);
    }
複製代碼
  • 1.TaggedPointer,直接return
  • 2.isa.nonpointer && !isa.weakly_referenced && !isa.has_assoc && !isa.has_cxx_dtor && !isa.has_sidetable_rc,直接釋放
  • 3.存在析構函數、關聯對象,都須要移除
  • 4.在引用計數表中檫除對象,弱引用表設置爲Nil
  • 5.釋放

總結:經過對retain,release,retainCount,dealloc源碼分析,上述5個問題都可以在裏面找到答案。此處就不在贅述。

四.弱引用weak

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.
}
複製代碼
  1. 首先調用weak_entry_for_referentwaek_table中獲取entry
  2. 而後調用remove_referrer,在entryreferrers中找到地址的索引,entry->referrers[index] = nil;entry->num_refs--;設置爲nil,並將num_refs減1
  3. 判斷entry是否還有值,沒有就在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->referrersnew_referrer先賦值到entry->inline_referrers[i] 而後將entry->inline_referrers循環對應拷貝到new_referrersnew_referrers賦值給entry->referrers = new_referrers; (2)entry不存在, 建立⼀個weak_entry_treferent加⼊到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

自動釋放池介紹

AutoReleasePoolARC引入的,用於管理對象的引用計數。 如下是AutoReleasePool的幾個要點:

  • 一個線程的自動釋放池是一種棧形式的指針集合,先進後出;
  • 每一個指針要麼是要釋放的對象,要麼是池的邊界,即自動釋放池邊界;
  • 池token是指向該池邊界的指針。當池被彈出時,全部比哨兵還熱的對象都被釋放;
  • 這個棧是一個雙向鏈表的頁面列表。根據須要添加和刪除頁面。
  • 線程本地存儲指向熱頁,其中存儲新的自動釋放的對象。

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節點爲nil
  • AutoreleasePoolPage *child:指向子節點,最後一個child節點爲nil
  • uint32_t const depth:表明深度,從0開始,遞增+1
  • uint32_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

atautoreleasepoolobj = 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);
        }
    }
複製代碼
  • 獲取當前AutoreleasePoolPagehotPage
  • 存在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個對象。

objc_autoreleasePoolPop

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對應的page
  • popPage<false>(token, page, stop);開始pop
template<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);釋放對象
  • page爲空,直接釋放這個page,若是有child,將child也kill
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.

autorelease

前面已經介紹了objc_autoreleasePoolPushobjc_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相似,這裏就不在贅述了。

自動釋放池、RunLoop

  • 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 = ()}}"
     ),
複製代碼
相關文章
相關標籤/搜索