iOS底層探索--內存管理

1. 五大分區

在一個4G內存的移動設備中,內核區約佔1GB
內存分區:代碼段、數據段、BSS段,棧區,堆區。棧區地址一般爲0x7開頭,堆區地址一般爲0x6開頭。數據段一般0x1開頭。

0x70000000對其進行轉換,剛好爲3GB

  • 棧區:存儲函數,方法,快速高效,
  • 堆區:通過alloc分配的對象,block copy,靈活方便,數據適應面廣泛,
  • BSS段:未初始化的全局變量,靜態變量,程序結束後有系統釋放。
  • 數據段:初始化的全局變量,靜態變量,程序結束後有系統釋放。
  • 代碼段:程序代碼,加載到內存中

棧的內存是訪問寄存器直接訪問其內存空間,堆裏的對象的訪問是通過存在棧區的指針存儲的地址,再找到堆區對應的地址。

全局變量和局部變量在內存中是否有區別?有什麼區別?

  • 存儲位置不同全局變量存在相應的全局存儲區域。局部變量定義在局部的空間,存儲在棧中

Block中是否可以直接修改全局變量

Block中可以修改全局變量。

全局靜態變量的修改

LGPerson中,定義一個全局變量personNum,並定義兩個方法,對全局變量++,然後在ViewController調用打印,結果是什麼樣的?

static int personNum = 100;

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson : NSObject

- (void)run;
+ (void)eat;
@end

#import "LGPerson.h"

@implementation LGPerson
- (void)run{
    personNum ++;
    NSLog(@"LGPerson內部:%@-%p--%d",self,&personNum,personNum);
}

+ (void)eat{
    personNum ++;
    NSLog(@"LGPerson內部:%@-%p--%d",self,&personNum,personNum);
}

- (NSString *)description{
    return @"";
}

@end

NSLog(@"vc:%p--%d",&personNum,personNum); // 100
personNum = 10000;
NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
[[LGPerson new] run]; // 100 + 1 = 101
NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
[LGPerson eat]; // 102
NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
[[LGPerson alloc] cate_method];

打印結果如下:

static修飾的靜態變量,只針對文件有效。在vc中和LGPerson中的兩個全局變量的地址不相同。

2. TaggedPointer

使用TaggedPointer存儲小對象NSNumber、NSDate,優化內存管理。

首先,看下面的代碼,能正常執行麼?點擊屏幕時會有什麼問題?

- (void)taggedPointerDemo {
  
    self.queue = dispatch_queue_create("com.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"cooci"];
             NSLog(@"%@",self.nameStr);
        });
    }
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    

    
    NSLog(@"來了");
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"和諧學習不急不躁"];
            NSLog(@"%@",self.nameStr);
        });
    }
}

通過測試,上述代碼,能正常執行,當點擊屏幕時,發生崩潰。那麼爲什麼在點擊屏幕是,會發生崩潰呢?

其實在多線程代碼塊中賦值,打印,是調用的settergetter,當settergetter加入多線程時,就會不安全。

setter方法底層是retian newvalue,然後realase oldvalue。多加入多線程時,就會出現多次釋放,造成野指針。

那麼,爲什麼第一段能夠正常執行呢?

通過上面的斷點調試,發現第一段代碼中_nameStr的類型並不是NSString而是taggedPointer類型,而第二段中是NSString類型。

接下來看一下objc_releaseobjc_retain的源碼:

void 
objc_release(id obj)
{
    if (!obj) return;
    if (obj->isTaggedPointer()) return;
    return obj->release();
}

objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}

release時,先判斷是否是isTaggedPointer,是,則直接返回,並沒有真的進行release操作,而在retain時也是同樣的操作,先判斷isTaggedPointer,並沒有進行retain,這也就解釋了爲什麼第一段代碼能正常執行,因爲其底層並沒有retainrelease,即使搭配多線程,也不會出現多次釋放的問題,也就不會出現野指針,也不會崩潰。

其實在read_images中的的initializeTaggedPointerObfuscator()中,會初始化一個objc_debug_taggedpointer_obfuscator,在構造TaggedPointer時,通過對這個值的^操作,進行編碼和解碼。

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;
    }
}

initializeTaggedPointerObfuscator中,在iOS之前低版本時,objc_debug_taggedpointer_obfuscator = 0,之後的版本objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK

在構建TaggedPointer時,會進行編碼,在獲取TaggedPointer時,解碼。

_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);
    }
}

_objc_getTaggedPointerTag(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;
    uintptr_t extTag =   (value >> _OBJC_TAG_EXT_INDEX_SHIFT) & _OBJC_TAG_EXT_INDEX_MASK;
    if (basicTag == _OBJC_TAG_INDEX_MASK) {
        return (objc_tag_index_t)(extTag + OBJC_TAG_First52BitPayload);
    } else {
        return (objc_tag_index_t)basicTag;
    }
}

其實編碼和解碼的操作就是與上objc_debug_taggedpointer_obfuscator

_objc_encodeTaggedPointer(uintptr_t ptr)
{
    return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}

/**
 1000 0001
^0001 1000
 
 1001 1001
^0001 1000
 1000 0001
 */

static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

我們可以調用解碼方法,來打印一下具體的TaggedPointer

    NSString *str1 = [NSString stringWithFormat:@"a"];
    NSString *str2 = [NSString stringWithFormat:@"b"];

    NSLog(@"%p-%@",str1,str1);
    NSLog(@"%p-%@",str2,str2);
    NSLog(@"0x%lx",_objc_decodeTaggedPointer_(str2));

    NSNumber *number1 = @1;
    NSNumber *number2 = @1;
    NSNumber *number3 = @2.0;
    NSNumber *number4 = @3.2;
    
    NSLog(@"%@-%p-%@ - 0x%lx",object_getClass(number1),number1,number1,_objc_decodeTaggedPointer_(number1));
    NSLog(@"0x%lx",_objc_decodeTaggedPointer_(number2));
    NSLog(@"0x%lx",_objc_decodeTaggedPointer_(number3));
    NSLog(@"0x%lx",_objc_decodeTaggedPointer_(number4));

打印結果:

上圖打印結果中,0xb000000000000012b表示數字,1就是變量的值。

不同類型的標記:

{
    // 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_NSColor           = 16,
    OBJC_TAG_UIColor           = 17,
    OBJC_TAG_CGColor           = 18,
    OBJC_TAG_NSIndexSet        = 19,

    OBJC_TAG_First60BitPayload = 0, 
    OBJC_TAG_Last60BitPayload  = 6, 
    OBJC_TAG_First52BitPayload = 8, 
    OBJC_TAG_Last52BitPayload  = 263, 

    OBJC_TAG_RESERVED_264      = 264
};

Tagged Pointer指針的值不再是地址了,而是真正的值。所以,實際上它不再 是一個對象了,它只是一個披着對象皮的普通變量而已。所以,它的內存並不存儲 在堆中,也不需要mallocfree,也不用retainrelease

在內存讀取上有着3倍的效率,創建時比以前快106倍,一般一個變量的位數在8-10位時,系統默認會使用Tagged Pointer

3.NONPOINTER_ISA的優化

通過對NONPOINTER_ISA64個字節位置的存儲,來內存管理。
isa結構:

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};

  • nonpointer:表示是否對 isa 指針開啓指針優化

    0:純isa指針,1:不止是類對象地址,isa 中包含了類信息、對象的引用計數當對象引用技術大於 10 時,則需要借用該變量存儲進位等

  • 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

3. retain & release & retainCount & dealloc分析

retain 和 release 分析

首先我們看一下retain的源碼:

objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}

從源碼中可以看出,在retain時,先判斷是否是isTaggedPointer,是則直接返回,不是,則開始retain

最終進入到rootRetain方法中。

objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    // retain 引用計數處理
    // 
    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        // ✅ 判斷不是nonpointer,散列表的引用計數表 進行處理 ++
        if (slowpath(!newisa.nonpointer)) {
            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;
        }
        // ✅ isa 中extra_rc++
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // 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;
}

rootRetain中,對引用計數進行處理,先獲取isa.bits

  • 判斷是不是nonpointer isa,如果不是nonpointer,在散列表中對引用計數進行++(先sidetable_unlock,在sidetable_retain())。

在對散列邊解鎖時(sidetable_unlock),


先從SideTables()中(從安全和性能的角度考慮,sidetable有多張表),找到對應的散列表(底層是哈希結構,通過下標快速查找到對應table),進行開鎖。然後sidetable_retain()

sidetable_retain時,

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;
}

先加鎖,然後存儲引用計數++(refcntStorage += 往左偏移兩位),然後再解鎖。

爲什麼是偏移兩位呢?


因爲前兩位都不是引用計數的標識位,第一位是弱引用weak標識位,第二位析構標識位

  • 判斷是不是正在析構

  • 判斷是nonpointer isa,則addc(newisa.bits, RC_ONE, 0, &carry)。對isa中的extra_rc++

    其實就是平移RC_ONE(在真機上是56位)位,找到extra_rc(nonpointer isa的引用計數存在這個地方),對其進行++

  • 當時nonpointer isa時,會判斷是否是slowpath(carry),即:extra_rc的引用計數是否存滿,

    • 當存滿時,會isa中的引用計數砍去一半,然後修改isa中引進計數借位標識,然後將另一半的引用計數存儲到散列表

將一般的引用計數存儲到散列表中,如下:

sidetable_addExtraRC_nolock源碼如下:

 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);

    if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;

    uintptr_t carry;
    size_t newRefcnt = 
        addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
    if (carry) {
        refcntStorage =
            SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
        return true;
    }
    else {
        refcntStorage = newRefcnt;
        return false;
    }
}

分析完了retain,那麼release就相對的比較簡單了,最終也會進入到rootRelease方法,先查看源碼:

objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    // ✅1. 判斷TaggedPointer
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        // ✅2. 判斷是nonpointer,當不是nonpointer時,獲取對應散列表,對引用計數表--
        if (slowpath(!newisa.nonpointer)) {
            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
        // ✅3.是nonpointer,對extra_rc--
        uintptr_t carry;
        newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc--
        // ✅4.當減到一定程度時,直接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.
        // ✅ 將引用計數表中的引用計數,移到extra_rc中
        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.
        // ✅對移出來的引用計數大於0時
        if (borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            // ✅將移出來的引用計數加到extra_rc中。
            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.
        }
    }

    // Really deallocate.

    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) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}

rootRelease中,先判斷TaggedPointer,然後判斷是否nonpointer isa,當不是nonpointer時,獲取對應散列表,對引用計數表–,當時nonpointer isa時,對extra_rc--,當減到一定程度時,直接調用underflow,判斷引用計數借用標識,將暫存到引用計數表中的引用計數,存到extra_rc中。

小結:

retain
1. 判斷是否是nonpointer isa,不是,則對散列表處理,對引用計數表處理
2. 是nonpointer isa,對extra_rc++
3. 超出時,將extra_rc中的一半存在儲到引用計數表中
爲什麼超出時不全部存在引用計數表中?
散列表要開鎖解鎖,優先選擇extra_rc

release
1. 先判斷TaggedPointer,
2. 判斷是否nonpointer isa,當不是nonpointer時,獲取對應散列表,對引用計數表--,
3. 當時 nonpointer isa 時,對 extra_rc--
4. 當引用計數減到一定程度時,直接調用 underflow
5. underflow中,判斷引用計數借用標識,將暫存到引用計數表中的引用計數,存到 extra_rc 中。

RetainCount 分析

首先我們看下面的一個問題:

    NSObject *objc = [NSObject alloc];
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)objc));

問:上面的代碼,引用計數打印是多少?alloc出來的對象,引用計數是多少?

這個打印結果,我們都知道是1,那麼alloc出來的對象引用計數真的是1麼?

接下來,我們深入看一下RetainCount的源碼,如下:

inline uintptr_t 
objc_object::rootRetainCount() // 1
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        // bits.extra_rc = 0;
        // ✅ 對isa 的 bits.extra_rc + 1,即對引用計數+1
        uintptr_t rc = 1 + bits.extra_rc; // isa
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock(); // 散列表
        }
        sidetable_unlock();
        return rc; // 1
    }
    sidetable_unlock();
    return sidetable_retainCount();
}

通過源碼的斷點調試,發現alloc出來的對象的引用計數extra_rc0,而通過retainCount打印出來的引用計數爲1,是通過uintptr_t rc = 1 + bits.extra_rc得來的,而單純alloc出來的對象的引用計數爲0,默認給1,防止被錯誤釋放

dealloc分析

dealloc底層,必然會調用rootDealloc()方法,源碼如下:

inline void
objc_object::rootDealloc()
{
    // ✅ 判斷是TaggedPointer,直接返回,不需要dealloc
    if (isTaggedPointer()) return;  // fixme necessary?
    // ✅ 判斷是不是nonponiter isa,不是則直接free
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        // ✅ 是nonponinter isa
        object_dispose((id)this);
    }
}

rootDealloc中,會講過上面的幾個判斷,當不是nonponinter isa時,調用object_dispose((id)this)方法。

object_dispose中,通過objc_destructInstance(obj)cxx關聯對象進行釋放,然後通過obj->clearDeallocating()weak表引用計數表

object_dispose(id obj)
{
    if (!obj) return nil;
    // weak
    // cxx
    // 關聯對象
    // ISA 64
    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        // 移除關聯對象
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

清除weak表引用計數表

objc_object::clearDeallocating_slow()
{
    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    // 清除weak表
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) { // 清理引用計數表
        table.refcnts.erase(this);
    }
    table.unlock();
}

小結:

dealloc 底層調用 rootDealloc,
rootDealloc 判斷 aggedPointer 直接返回,然後判斷不是nonponiter isa 則直接釋放
判斷是nonponiter isa則調用 object_dispose 開始釋放
在 object_dispose 中,對cxx、關聯對象表、weak表和引用計數表進行釋放。

4. 循環引用

在開發中,我們經常會使用NSTimer定時器,如下:

// 定義屬性
@property (nonatomic, strong) NSTimer       *timer;

// 初始化
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];

- (void)fireHome{
    num++;
    NSLog(@"hello word - %d",num);
}


// 釋放
- (void)dealloc{
    [self.timer invalidate];
    self.timer = nil;
    NSLog(@"%s",__func__);
}

首先在VC中定義timer屬性,在dealloc中,調用[self.timer invalidate] self.timer = nil,對timer進行析構和置空。

當我們把dealloc中的代碼去掉,當頻繁的pushpop頁面時,就會出現問題,而造成問題的原因是循環引用

首先VCtimer是強持有,timertarget屬性強持有,這樣就造成了循環引用。

那麼怎麼解決這個循環引用呢?

按照通常的處理方法,就是使用weakSelf,即下面的方式:

self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];

通過測試,發現並沒有打破self -> block -> self的循環引用。那麼爲什麼呢?

因爲,NSTimer 依賴於 NSRunLooptimer需要加到NSRunLoop才能運行,NSRunLoopNSTimer強持有,timerself或者weakself,強持有,timer無法釋放,導致weakself無法釋放,self無法釋放。

那麼爲什麼block 的循環引用中,使用weakself可以解決,爲什麼這個不能釋放呢?

__weak typeof(self) weakSelf = self;

通過打印weakSelfself的地址如下:

通過上面打印發現weakSelfself是兩個不同的地址。經過weakSelf 弱引用後,並沒有對引用計數處理。

NSRunLoop -> timer -> weakSelf -> selfweakSelf間接操作self,間接強持有了self,所以無法釋放

block解決循環引用時,使用weakSelf,其實是一個臨時變量的指針地址,block強持有的是一個新的指針地址。所以打破了循環引用的問題。
self -> block -> weakSelf (臨時變量的指針地址)

block的循環引用操作的是對象地址,timer循環引用操作的是對象。

5. Timer循環引用的解決

  • pop的時候就銷燬timer
- (void)didMoveToParentViewController:(UIViewController *)parent{
    // 無論push 進來 還是 pop 出去 正常跑
    // 就算繼續push 到下一層 pop 回去還是繼續
    if (parent == nil) {
       [self.timer invalidate];
        self.timer = nil;
        NSLog(@"timer 走了");
    }
}
  • 中介者模式

定義一個self.target = [[NSObject alloc] init],讓其作爲target來響應。

self.target = [[NSObject alloc] init];
class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc, "v@:");
self.timer = [NSTimer scheduledTimerWvoid fireHomeObjc(id obj){
    NSLog(@"%s -- %@",__func__,obj);
}

- (void)fireHome{
    num++;
    NSLog(@"hello word - %d",num);
}
ithTimeInterval:1 target:self.target selector:@selector(fireHome) userInfo:nil repeats:YES];


  • 封裝一箇中間類
@interface LGTimerWapper : NSObject

- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
- (void)lg_invalidate;

@end

#import "LGTimerWapper.h"
#import <objc/message.h>

@interface LGTimerWapper()
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL aSelector;
@property (nonatomic, strong) NSTimer *timer;

@end

@implementation LGTimerWapper

- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
    if (self == [super init]) {
        self.target     = aTarget; // vc
        self.aSelector  = aSelector; // 方法 -- vc 釋放
        
        if ([self.target respondsToSelector:self.aSelector]) {
            Method method    = class_getInstanceMethod([self.target class], aSelector);
            const char *type = method_getTypeEncoding(method);
            class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type);
            
            // 難點: lgtimer
            // 無法響應
            // 時間點: timer invalid
            // vc -> lgtimerwarpper
            // runloop -> timer -> lgtimerwarpper
            self.timer      = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
        }
    }
    return self;
}

// 一直跑 runloop
void fireHomeWapper(LGTimerWapper *warpper){
    
    if (warpper.target) {
        void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
         lg_msgSend((__bridge void *)(warpper.target), warpper.aSelector,warpper.timer);
    }else{ // warpper.target
        [warpper.timer invalidate];
        warpper.timer = nil;
    }
}


- (void)lg_invalidate{
    [self.timer invalidate];
    self.timer = nil;
}

- (void)dealloc{
    NSLog(@"%s",__func__);
}

@end


self.timerWapper = [[LGTimerWapper alloc] lg_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];

  • 使用proxy 虛基類的方式
@interface LGProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end


#import "LGProxy.h"

@interface LGProxy()
@property (nonatomic, weak) id object;
@end

@implementation LGProxy
+ (instancetype)proxyWithTransformObject:(id)object{
    LGProxy *proxy = [LGProxy alloc];
    proxy.object = object;
    return proxy;
}

// 僅僅添加了weak類型的屬性還不夠,爲了保證中間件能夠響應外部self的事件,需要通過消息轉發機制,讓實際的響應target還是外部self,這一步至關重要,主要涉及到runtime的消息機制。
// 轉移
-(id)forwardingTargetForSelector:(SEL)aSelector {
    return self.object;
}
@end

self.proxy = [LGProxy proxyWithTransformObject:self];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];


6. 自動釋放池 AutoreleasePool

首先,我們來看幾道面試題目:

  • 題目1:臨時變量什麼時候釋放
  • 題目2:自動釋放池原理
  • 題目3:自動釋放池能否嵌套使用

接下來,我們來探索一下自動釋放池AutoreleasePool的底層,首先在main.m文件中寫一行代碼,如下:

通過clang -rewrite-objc main.m -o main.cpp將其編譯成cpp文件,查看AutoreleasePool的底層結構。

編譯後查看main.cpp


自動釋放池AutoreleasePool,在底層是一個__AtAutoreleasePool類型的結構體,如下:

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

__AtAutoreleasePool結構體裏,有構造函數析構函數

當我們創建__AtAutoreleasePool這樣一個結構體時,就會調用構造函數析構函數

AutoreleasePoolPage分析

在調用構造函數時,會調用objc_autoreleasePoolPush(),由函數名可直接定位到objc源碼中,接着在objc源碼中查看objc_autoreleasePoolPush()函數的具體實現,

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

其中會通過一個AutoreleasePoolPage來調用push(),那麼AutoreleasePoolPage是什麼東西呢?我們查看一下源碼。

發現AutoreleasePoolPage繼承自私有類AutoreleasePoolPageData

/***********************************************************************
   Autorelease pool implementation
    // 先進後出
   A thread's autorelease pool is a stack of pointers. 
   Each pointer is either an object to release, or POOL_BOUNDARY which is 
     an autorelease pool boundary.
   A pool token is a pointer to the POOL_BOUNDARY for that pool. When 
     the pool is popped, every object hotter than the sentinel is released.
   The stack is divided into a doubly-linked list of pages. Pages are added 
     and deleted as necessary. 
   Thread-local storage points to the hot page, where newly autoreleased 
     objects are stored. 
**********************************************************************/

BREAKPOINT_FUNCTION(void objc_autoreleaseNoPool(id obj));
BREAKPOINT_FUNCTION(void objc_autoreleasePoolInvalid(const void *token));

class AutoreleasePoolPage : private AutoreleasePoolPageData
{
	friend struct thread_data_t;
	
	...
}

從上面的註釋可以看出Autorelease池是實現:

  1. 線程的自動釋放池是指針的堆棧。每個指針要麼是要釋放的對象,要麼是要釋放的POOL_BOUNDARY(自動釋放池邊界可以理解爲哨兵,釋放到這個位置,就到邊界了,釋放完了)。

  2. 池標記是指向該池的POOL_BOUNDARY的指針。當池被彈出,每一個比哨兵更熱的物體被釋放。

  3. 該堆棧被劃分爲一個頁面的雙鏈接列表。頁面在必要時進行添加和刪除。

  4. 新的自動釋放的對象,存儲在聚焦頁(hot Page

AutoreleasePoolPageData的具體源碼實現如下:

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)
	{
	}
};

上面的代碼中可以看出,在AutoreleasePoolPageData中有一系列的屬性。那麼所有AutoreleasePool的對象都有上面的屬性。

每個屬性的含義:

  • magic:用來校驗AutoreleasePoolPage的結構是否完成
  • next :指向新添加的autoreleased對象的下一個位置,初始化時指向begin()
  • thread:指向當前線程
  • parent:指向父結點,第一個結點的parent值爲nil
  • child :指向子結點,最後一個結點的子結點值爲nil
  • depth :代表深度,從0開始,往後遞增1
  • hiwat :代表 high water mark 最大入棧數量標記

而屬性parentchild證明了上面註釋所解釋的自動釋放池是雙向鏈表結構

在構建AutoreleasePoolPage時,會調用AutoreleasePoolPageData的構建函數,傳入參數,如下:

傳入的第一個參數begin(),實現如下:

那麼爲什麼要this+sizeof(*this)呢?通過斷點調試,並打印sizeof(*this)的值,如上圖,爲56

那麼爲什麼要 +56呢?

其實是剛好往下偏移了AutoreleasePoolPageData
屬性所佔的空間(AutoreleasePoolPageData的屬性中next、thread、parent、child都是指針類型,佔8字節,depth、hiwat各佔4字節,magic是一個結構體,所佔內存是由結構體內部屬性決定,所以佔4*4個字節,屬性共佔56字節),開始存儲autoreleased對象

我們可以通過打印自動釋放池,來驗證一下,在main()中寫一下代碼:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        // 1 + 504 + 505 + 505
        for (int i = 0; i < 5; i++) {
            NSObject *objc = [[NSObject alloc] autorelease];
            NSLog(@"objc = %@",objc);
        }
        _objc_autoreleasePoolPrint();
    }
    return 0;
}

打印結果如下:


從打印結果看出,有6個釋放對象,包括循環創建加入的5個和一個邊界哨兵對象,而從0x1018030000x101803038剛好是56字節,正是AutoreleasePoolPageData的屬性,進而驗證了上面的推測。

自動釋放池添加對象的數量

自動釋放池是不是能無限添加對象呢?

我們對上面的循環進行修改,循環505次

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        // 1 + 504 + 505 + 505
        for (int i = 0; i < 505; i++) {
            NSObject *objc = [[NSObject alloc] autorelease];
//            NSLog(@"objc = %@",objc);
        }
        _objc_autoreleasePoolPrint();
    }
    return 0;
}

打印結果:

從打印結果看出,有506個對象,其進行了分頁,第一個page(full),已存儲滿,而最後一個對象存儲在page(hot)

由此可見,AutoreleasePool每一頁剛好存儲505個8字節的對象,而第一頁存儲的是1(邊界對象)+504(添加到釋放池的對象)

通過查看AutoreleasePoolPage源碼,

最終得到size = 4096,減去屬性所佔的56個字節,剛好是505個8字節對象

所以,自動釋放池AutoreleasePool第一頁存儲504個8字節對象,其他頁505個8字節對象

objc_autoreleasePoolPush()分析

當自動釋放池創建進行析構時,會調用push()

push()函數源碼

    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;
    }

push()中,進入autoreleaseFast(POOL_BOUNDARY)函數,源碼如下:

    static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        // ✅ 1. 判斷page存在&非滿的狀態
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) { // ✅ 判斷page滿了
            return autoreleaseFullPage(obj, page);
        } else { // ✅ 沒有page
            return autoreleaseNoPage(obj);
        }
    }
  • 首先,判斷page存在並且非滿的狀態的情況下,將對象添加的page
    id *add(id obj)
    {
        ASSERT(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();
        return ret;
    }

獲取next指針,將要添加的obj存儲到next指針指向的位置,然後next指針往下偏移8字節,準備下一次存儲。

  • 然後,判斷page已經滿的情況,調用autoreleaseFullPage()
 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);
        // ✅循環判斷page的child是否full,最後一頁的child爲nil,然後創建新的page
        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());
        // ✅將新的page設置爲聚焦頁,(打印時,顯示page(hot))
        setHotPage(page);
        return page->add(obj);
    }

autoreleaseFullPage()函數中,遞歸,然後創建一個新的page,並設置爲HotPage

  • 最後,判斷沒有page的情況,經過一系列判斷,然後創建一個新的page,並設置爲
    HotPage

注意

而在MRC情況下,autorelease也會一步一步最終調用autoreleaseFast()函數,進入上面的判斷流程。

autorelease底層調用順序:

- (id)autorelease {
    return _objc_rootAutorelease(self);
}

_objc_rootAutorelease(id obj)
{
    ASSERT(obj);
    return obj->rootAutorelease();
}


inline id 
objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

id 
objc_object::rootAutorelease2()
{
    ASSERT(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

    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;
    }

objc_autoreleasePoolPop()分析

objc_autoreleasePoolPop底層調用如下:

void
_objc_autoreleasePoolPop(void *ctxt)
{
    objc_autoreleasePoolPop(ctxt);
}

objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

其中ctxt上下文參數,傳入的關聯pushpop的對象,即:atautoreleasepoolobj對象(通過上面的cpp文件得知),atautoreleasepoolobj對象是進行push時返回的對象,最終傳到pop中。

pop()實現如下:

static inline void
    pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;
        // ✅ 判斷標識是否爲空,
        // 爲空:沒有壓棧對象,爲空直接將標識設置爲begin,即佔位的地方。
        // 不爲空:返回當前page
        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);
    }

pop()中,先判斷token標識是否爲空,然後進行越界判斷,最終執行popPage()開始釋放,

popPage()源碼如下:

static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();
        // ✅ release 對象
        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進行釋放(殺表)。而具體的對象釋放,則如下:

 void releaseUntil(id *stop) 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        // ✅ 從next位置開始,一直釋放,next--,知道stop位置
        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
            // ✅ empty (next == begin())當next == begin()時,page釋放完了
            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
    }

解讀:從next位置開始,一直釋放對象,next--,直到stop位置,每一個page釋放到next == begin()時,該page釋放完。

自動釋放池嵌套

main中,寫一下代碼:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        // 1 + 504 + 505 + 505
        NSObject *objc = [[NSObject alloc] autorelease];
            NSLog(@"objc = %@",objc);
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                @autoreleasepool {
                    NSObject *obj = [[NSObject alloc] autorelease];
                    NSLog(@"obj = %@",obj);
                    _objc_autoreleasePoolPrint();
                }
            });
            _objc_autoreleasePoolPrint();

    }

    sleep(2);
    return 0;
}

打印結果如下:

當兩個autoreleasePool進行嵌套時,只會創建一個page,但是有兩個哨兵

小結

1. autoreleasePool和線程關聯,不同的線程autoreleasePool地址不同
2. 在創建autoreleasePool時,先構造,調用objc_autoreleasePoolPush,再析構,調用objc_autoreleasePoolPop
3. 在壓棧對象時,判斷page存在且不滿,然後添加,不存在或者已滿的時候,創建新的page
4. 自動釋放池,第一頁存儲504個8字節對象,其他頁505個8字節對象
5. 對象出棧時,先對對象release,然後釋放page
6. 在MRC情況下,autorelease也會一步一步最終調用autoreleaseFast()函數,然後進行判斷,然後將對象入棧

6. RunLoop

RunLoop是一個運行循環,底層是一個do...while循環。
作用:

  • 保持程序持續運行,不會掛掉。
  • 處理APP中的各種事件(觸摸,定時器,performSelector
  • 節省CPU資源,提供程序的性能,該做事做啥,該休息休息

RunLoop的六大事件:

RunLoop 和 線程的關係

我們通常通過下面的方式,獲取主運行循環和當前運行循環。

// 主運行循環
CFRunLoopRef mainRunloop = CFRunLoopGetMain();
// 當前運行循環
CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();

CFRunLoop源碼中,CFRunLoopGetMain調用_CFRunLoopGet0方法。而CFRunLoopGetCurrent獲取當前線程的底層也是調用_CFRunLoopGet0方法。

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

_CFRunLoopGet0函數實現:

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    // 判斷runloop
    if (!__CFRunLoops) {
        __CFSpinUnlock(&loopsLock);
        // ✅創建一個dict
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        // ✅創建一個mainLoop
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        // ✅通過key-value的形式,將線程和mainloop存在dict中
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
        __CFSpinLock(&loopsLock);
    }
    
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFSpinUnlock(&loopsLock);
        CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

底層線程runLoopkey-value的形式一一對應的。

RunLoop也是一個對象,裏面有一系列的屬性,比如(線程、commonModescommonModeItemscurrentMode)。

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* locked for accessing mode list */
    __CFPort _wakeUpPort;            // used for CFRunLoopWakeUp
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};

runLoop和線程一對一綁定,runLoop底層依賴一個CFRunLoopMode,是一對多的關係,而commonModes中的items(比如:sourcetimerobserve)也是一對多的關係。

子線程runloop默認不開啓,需要run一下。

接下來,我們以NSTimer爲例,分析一下是如何回調的,首先如下斷點,打印調用堆棧,

發現,先調用__CFRunLoopRun,然後調用__CFRunLoopDoTimers

__CFRunLoopDoTimers中:

static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {    /* DOES CALLOUT */
    Boolean timerHandled = false;
    CFMutableArrayRef timers = NULL;
    // ✅ 準備times
    for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
        
        if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
            if (rlt->_fireTSR <= limitTSR) {
                if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
                CFArrayAppendValue(timers, rlt);
            }
        }
    }
    
    // ✅ 取出所有的timer ,開始__CFRunLoopDoTimer
    for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
        // ✅ 調用 __CFRunLoopDoTimer
        Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
        timerHandled = timerHandled || did;
    }
    if (timers) CFRelease(timers);
    return timerHandled;
}

先準備timer,然後取出所有的timer開始__CFRunLoopDoTimer,然後釋放timers

__CFRunLoopDoTimer中,通過下面的代碼進行回調,

__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);

也驗證了調用堆棧的方法調用

同理,mode中的其他事件(sources0source1observeblockGCD)也是通過這樣的方式回調的。

runloop中會添加很多itemsitems的運行依賴於mode(UITrackingRunLoopMode,
GSEventReceiveRunLoopMode,
kCFRunLoopDefaultMode)。

RunLoop原理

void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

do...while循環,如果返回的結果不是stop或者finished,一直執行CFRunLoopRunSpecific,一直循環,是則,跳出循環。

CFRunLoopRunSpecific中,

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    //根據modeName找到本次運行的mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    //如果沒找到 || mode中沒有註冊任何事件,則就此停止,不進入循環
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        Boolean did = false;
        if (currentMode) __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopUnlock(rl);
        return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    //取上一次運行的mode
    CFRunLoopModeRef previousMode = rl->_currentMode;
    //如果本次mode和上次的mode一致
    rl->_currentMode = currentMode;
    //初始化一個result爲kCFRunLoopRunFinished
    int32_t result = kCFRunLoopRunFinished;
    
    if (currentMode->_observerMask & kCFRunLoopEntry )
        /// ✅ 1. 通知 Observers: RunLoop 即將進入 loop。
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    if (currentMode->_observerMask & kCFRunLoopExit )
        /// ✅ 10. 通知 Observers: RunLoop 即將退出。
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

  1. 先根據modeName獲取mode,如果沒有找到mode或者mode中沒有註冊事件,則直接停止,不進入循環,
  2. 然後取上一次運行的mode對當前mode進行對比
  3. 判斷是否是kCFRunLoopEntry,是則__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry),通知 ObserverRunLoop即將進入loop
  4. 判斷是否是kCFRunLoopExit,是則__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit),通知 ObserverRunLoop即將退出。

__CFRunLoopRun中,先進行超時判斷和runloop狀態的判斷,然後開始一個do...while循環,

在這個do...while循環中,循環執行下面的判斷

  • 依次判斷

    rlm->_observerMask & kCFRunLoopBeforeTimers,是則,通知ObserversRunLoop即將出發Timer回調;

    即:__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers)

  • 判斷rlm->_observerMask & kCFRunLoopBeforeSources,是則,通知ObserversRunLoop即將出發Sources0(非port)回調;

    即:__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)

    然後執行被加入的block__CFRunLoopDoBlocks(rl, rlm)

  • RunLoop 觸發 Source0 (非port) 回調,即處理Source0

  • 如果有 Source1 (基於port) 處於 ready 狀態,直接處理這個 Source1 然後跳轉去處理消息。

  • 通知 Observers: RunLoop 的線程即將進入休眠(sleep)。然後設置RunLoop爲休眠狀態

然後進入一個do...while內循環,用於接收等待端口的消息,進入這個循環後,線程進入休眠,直達收到下面的消息才被喚醒,跳出循環,執行runloop

  1. 一個基於 port 的Source 的事件。
  2. 一個 Timer 到時間了
  3. RunLoop 自身的超時時間到了
  4. 被其他什麼調用者手動喚醒

然後通知 Observers: RunLoop 的線程剛剛被喚醒;

然後處理被喚醒時的消息:

  1. 如果一個Timer 到時間了,觸發這個Timer的回調,__CFRunLoopDoTimers,然後準備times,循環執行__CFRunLoopDoTimer,最後調用__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info)回調方法。
  2. 如果有dispatchmain_queueblock,執行block__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg)
  3. 判斷是否是一個 Source1 (基於port) 事件

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章