iOS進階-內存管理

內存佈局

先上一內存佈局圖 c++

解釋

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

其餘面試

  • (0xC0000000 = 3221225472 = 3GB),因此從棧區到保留區一共佔有3GB
  • 通常咱們只討論棧區-text區這5大區域
  • 棧區向下增加,內存佔有小,處理裏速度快;堆區向上增加,內存佔有大,處理裏速度慢
  • bbs和data區在不區分是否初始化時,通常統稱全局區

地址開頭數組

  • 棧區內存地址:通常以 0x7開頭
  • 堆區內存地址:通常以 0x6開頭
  • data區、bbs區內存地址:通常以 0x1開頭

面試題

  • 一、全局變量和局部變量在內存中是否有區別?若是有,是什麼區別?

答:有。全局變量存在全局區(bbs區/data區),局部變量存在棧區安全

  • 二、下面Block 是否能夠直接修改全局變量?
static NSString *K_Name = @"lala";
- (void)test {
    [UIView animateWithDuration:1 animations:^{
       K_Name = @"dingding";
    }];
}
複製代碼

答:能夠,全局變量能夠全局訪問bash

  • 三、下面打印是怎樣的?
##### Person
//注意personNum是定義在Person.h文件中的靜態變量
static int personNum = 100;

@interface Person : NSObject
- (void)run;
+ (void)eat;
@end

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

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

@end

#### 分類 Person (ca)
#import "Person.h"
@interface Person (ca)
- (void)cate_method;
@end

@implementation Person (ca)
- (void)cate_method{
    NSLog(@"Person內部:%@-%p--%d",self,&personNum,personNum);
}

@end
複製代碼

題目數據結構

#### ViewController
- (void)viewDidLoad {
    [super viewDidLoad];

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

打印:多線程

vc:0x102730484--100
vc:0x102730484--10000
Person內部:-0x102730460--101
vc:0x102730484--10000
Person內部:LGPerson-0x102730460--102
vc:0x102730484--10000
Person內部:-0x102730488--100
複製代碼

:首先static變量時能夠邊修改的;static變量的做用域與對象、類、分類不要緊,只與文件有關係;app

內存管理方案

apple在內存管理方面提供了三種方案(TaggetPointer、NONPOINTER_ISA、散列表),嚴謹的說應該是三種方案共同管理內存。less

TaggetPointer

介紹

  • 一、Tagged Pointer是專⻔⽤來存儲⼩的對象,例如NSNumber和NSDate
  • 二、Tagged Pointer指針的值再也不是地址了,⽽是真正的值。因此,實際上它再也不是⼀個對象了,它只是⼀個披着對象⽪的普通變量⽽已。因此,它的內存並不存儲 在堆中,也不須要malloc和free
  • 三、在內存讀取上有着3倍的效率,建立時⽐之前快106倍。

原理

首先理解一個操做:對同一個數值^(異或)操做兩次,獲得的仍是原來的數值。eg:async

1000 0001
^  0001 1000
------------
   1001 1001
 
   1001 1001
^  0001 1000
------------
   1000 0001
複製代碼

下面是TaggedPointer 對象的源碼,能夠看到,也是對同一個數值(objc_debug_taggedpointer_obfuscator)進行^異或操做

// 存值
static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value) {
    //objc_tag_index_t 是一個枚舉值 用於標定不一樣tag類型,根據不一樣的類型的tag值進行不一樣的左右移和MASK操做
    if (tag <= OBJC_TAG_Last60BitPayload) {
        //將類型tag和值Value進行打包(左右移和MASK操做),獲得一個result
        uintptr_t result =
            (_OBJC_TAG_MASK | 
             ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | 
             ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
        // 用 result進行encode ^ 或操做,返回一個指針
        return _objc_encodeTaggedPointer(result);
    } else {//下面操做同樣
        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);
    }
}

//取值
static inline uintptr_t
_objc_getTaggedPointerValue(const void * _Nullable ptr) {
    //將指針ptr先decode ^ 操做取出value(就是上面的result)
    uintptr_t value = _objc_decodeTaggedPointer(ptr);
    //value解包(左右移和MASK操做)獲得value
    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;
    }
}

extern uintptr_t objc_debug_taggedpointer_obfuscator;
//encode ^ 操做
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr) {
    return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}
//decode ^ 操做
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr) {
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}

//objc_tag_index_t 是一個枚舉值 用於標定不一樣tag類型
enum objc_tag_index_t : uint16_t {
    // 60-bit payloads
    OBJC_TAG_NSAtom            = 0, 
    OBJC_TAG_1                 = 1, 
    OBJC_TAG_NSString          = 2, //NSString類的tag爲2
    OBJC_TAG_NSNumber          = 3, //NSNumber類的tag爲2
    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
};
複製代碼

源碼結論

  • 存值:當一個TaggedPointer對象在存值時,先將對象類型tag和值value打包(左右移和MASK操做)處理獲得一個result,而後result和一個隨機的常量值objc_debug_taggedpointer_obfuscator進行^異或操做返回一個包裝過的TaggedPointer指針
  • 取值 :取值的時候恰好相反,先用TaggedPointer指針和一個隨機的常量值objc_debug_taggedpointer_obfuscator進行^異或操做返回result,而後result解包獲得值value

面試題

  • 爲何兩個方法一個會崩潰,另外一個不會崩潰呢?
//MARK: - taggedPointer 面試題
@property (nonatomic, strong) NSString *nameStr;

- (void)taggedPointer_NOCrash {
    dispatch_queue_t queue = dispatch_queue_create("com.fun.cn", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i<10000; i++) {
        dispatch_async(queue, ^{
            self.nameStr = [NSString stringWithFormat:@"fun"];
             NSLog(@"%@",self.nameStr);
        });
    }
}
 
- (void)taggedPointer_Crash {
    dispatch_queue_t queue = dispatch_queue_create("com.fun1.cn", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i<10000; i++) {
        dispatch_async(queue, ^{
            self.nameStr = [NSString stringWithFormat:@"fun 好好學習.每天向上。發展體育運動,加強國民體質"];
            NSLog(@"%@",self.nameStr);
        });
    }
}
複製代碼

答:首先 這兩句代碼self.nameStr = [NSString stringWithFormat:@"fun 好好學習.每天向上。發展體育運動,加強國民體質"]; NSLog(@"%@",self.nameStr);表明着get/set方法;對象的get/set方法內部操做就是先新值 retian,後舊值 release;讓咱們看下retian/release源碼實現;

void
objc_storeStrong(id *location, id obj) {
    id prev = *location;
    if (obj == prev) {
        return;
    }
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}

__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();
}
複製代碼

很明顯正常對象的get/set方法內部操做就是先新值 retian,後舊值 release,在多線程訪問的狀況下,新值 retian,舊值 release調用錯亂,致使野指針或者空,因此崩潰; 可是若是是對象是taggedPointer對象時,對象不會進行retian/release操做,因此不會崩潰;而self.nameStr = [NSString stringWithFormat:@"fun"];nameStr是 NSTaggedPointerString類型;self.nameStr = [NSString stringWithFormat:@"fun 好好學習.每天向上。發展體育運動,加強國民體質"];nameStr是——NSCFString類型。

NONPOINTER_ISA

介紹

在最新的objc2源碼中可知萬物皆objc_object對象,在objc_object對象內部有一個isa屬性;這個isa有多是純指針,也有可能除包含指針外還包含其餘信息,例如對象的引用計數、是否被弱引用...這時這個isa就是NONPOINTER_ISAisaisa_t類型的聯合體,其內部經過位域技術儲存不少了對象的信息。

isa_t源碼

源碼中有註釋就再也不贅述了

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

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        // __arm64__ defined in isa.h
        //這裏把 ISA_BITFIELD 內部的宏直接寫進來了 以arm64構架說明
        
        /*表示是否對isa開啓指針優化 。0表明是純isa指針,1表明除了地址外,還包含了類的一些信息、對象的引用計數等。*/
        uintptr_t nonpointer        : 1;
        
        /*關聯對象標誌位*/
        uintptr_t has_assoc         : 1;
        
        /*該對象是否有C++或Objc的析構器,若是有析構函數,則須要作一些析構的邏輯處理,若是沒有,則能夠更快的釋放對象*/
        uintptr_t has_cxx_dtor      : 1;
        
        /*存在類指針的值,開啓指針優化的狀況下,arm64位中有33位來存儲類的指針*/
        uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/
        
        /*判斷當前對象是真的對象仍是一段沒有初始化的空間*/
        uintptr_t magic             : 6;
        
        /*是否被指向或者曾經指向一個ARC的弱變量,沒有弱引用的對象釋放的更快*/
        uintptr_t weakly_referenced : 1;
        
        /*是否正在釋放*/
        uintptr_t deallocating      : 1;
        
        /*是否有輔助的引用計數散列表*/
        uintptr_t has_sidetable_rc  : 1;
        
        /*表示該對象的引用計數值,滿了就會存在sidetable 中*/ 
        uintptr_t extra_rc          : 19;
    };
#endif
};
複製代碼

源碼中的extra_rc就是用來存儲引用計數的,具體原理放到下面的引用計數部分說明。

散列表--引用計數&弱引用計數

介紹

系統維護了一張全局的Hash表,裏面存了一張張SideTable散列表,而這個散列表中就儲存了對象的引用計數以及弱引用狀況。

內部結構

SideTable 內部結構

struct SideTable {
    spinlock_t slock;//鎖,用於控制數據訪問安全
    RefcountMap refcnts;//引用計數表s
    weak_table_t weak_table;//弱引用計數表s
    ...
    ...
};
複製代碼

結構解釋

  • 系統維護了一張全局Hash表,用於管理對象的引用計數和weak指針。內部就是一個個散列表SideTble,使用對象的內存地址做爲SideTable的Key(地址通過哈希計算)。而且不一樣的對象可能在同一張散列表SideTble中。
  • spinlock_t slock:鎖,用於控制這張散列表SideTble的數據訪問安全。
  • RefcountMap refcnts:引用計數表RefcountMap,用於儲存對象的引用計數狀況,下面會具體說明
  • weak_table_t weak_table:弱引用狀況表,用於儲存對象的弱引用狀況,下面會具體說明

RefcountMap 引用計數表

isa_textra_rc位引用計數滿了後會把一半的引用計數放到某個散列表SideTable中的引用計數表中;具體操做以下:

  • RefcountMap表內部是一個個BucketT(桶)連起來的Buckets(相似於數組),
  • 又經過對象內存的地址通過運算(這部分很複雜,我也說不太明白)獲得具體BucketT(桶)
  • BucketT(桶)裏面封裝了對象的引用計數。以後就是引用計數的+-了

weak_table_t 弱引用計數表

  • 一、weak_table_t 是個二維數組,裏面包含了一個個weak_table,weak_table裏面是一個個weak_entry
  • 二、當一個對象的屬性被設置成weak時,weak_table表中會查找當內部有沒有該對象的弱引用數組(weak_entry數組),若是有就直接插入這個屬性到這個weak_entry數組,沒有就先建立weak_entry數組再插入
  • 三、當對象被釋放時(delloc),會經過對象指針去查找weak_table沒有該對象的weak_entry數組,有的話遍歷weak_entry數組,將內部的屬性置爲nil;最後將這個weak_entry數組remove
爲何是 weak_entry數組

由於一個對象可能擁有多個弱應用屬性

舉例

咱們把系統維護的Hash表當成是一個男生宿舍樓

對象:學生
對象的內存地址:學生的姓名

hash表:一棟宿舍樓
SideTable:寢室
spinlock_t:寢室門上的鎖,管理學生出入

RefcountMap:某一個寢室
引用計數:寢室某個學生擁有的書本數

weak_table_t:另外一個寢室
weak_table:學生s
弱引用狀況就是學生擁有的女友數量的狀況
複製代碼

因此,不一樣的對象(學生)可能在同一個SideTable(寢室)中 -不一樣點:引用計數相似於一維數組,而弱引用狀況是一個二維數組

引用計數

在上面內存管理方案中已經介紹了TaggetPointer、NONPOINTER_ISA、散列表。下面說下引用計數具體如何工做的。引用計數的核心就是對象的retain、release方法

retain

直接看源碼,內有詳細的解釋,就再也不贅述

-(id) retain {
    return _objc_rootRetain(self);
}

NEVER_INLINE id
_objc_rootRetain(id obj) {
    ASSERT(obj);
    return obj->rootRetain();
}

ALWAYS_INLINE id 
objc_object::rootRetain() {
    return rootRetain(false, false);
}

ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow) {
    //判斷是不是TaggedPointer,是的話直接返回
    if (isTaggedPointer()) return (id)this;
    //用於記錄鎖狀態
    bool sideTableLocked = false;
    bool transcribeToSideTable = false;
    //初始化isa_t 用於後面賦值
    isa_t oldisa;
    isa_t newisa;

    // 真正 retain 引用計數處理
    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        //是否用到了sidetable輔助處理引用計數
        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;
            //sidetable內部處理引用計數
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides //判斷對象是否正在dealloc if (slowpath(tryRetain && newisa.deallocating)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); return nil; } //其實就是對isa的extra_rc變量進行+1,前面說到isa會存不少東西 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. //留一半的引用計數 //準備複製另外一半引用計數到sideTable 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))); //是否將引用計數遷移到sidetable中 if (slowpath(transcribeToSideTable)) { // Copy the other half of the retain counts to the side table. //從newisa.extra_rc複製一半的引用計數到sidetable中 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 //經過this內存地址拿到對應的SideTable SideTable& table = SideTables()[this]; //加鎖 table.lock(); //又經過this內存地址拿到對象的refcntStorage(內存存有引用計數) size_t& refcntStorage = table.refcnts[this]; if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) { //引用計數 +1 refcntStorage += SIDE_TABLE_RC_ONE; } //解鎖 table.unlock(); //返回對象 return (id)this; } 複製代碼

源碼簡介 1.判斷是否是TaggedPointer對象,若是是直接返回,不作引用計數處理 2.若是是NONPOINTER_ISA對象,那就對isa.extra_rc進行+1; 3.若是isa.extra_rc滿了,就取一半複製到sideTable中輔助儲存

release

release方法內部就是引用引用計數減一,就再也不解釋,基本和retain差很少,本身看源碼

AutoReleasePool自動釋放池

AutoReleasePool 是ARC引入的,用於管理對象的引用計數。

自動釋放池的實現文檔

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.

翻譯:

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

相關的數據結構

AutoreleasePoolPage內部結構

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結構體的類
  • __unsafe_unretained id *next:用來校驗AutoreleasePoolPage的結構是否完整
  • next: 指向最新添加的autopeleased對象的下一個位置,初始化時指向begin()
  • pthread_t const thread :至當前線程
  • AutoreleasePoolPage * const parent :指向父節點,第一個parent節點爲nil
  • AutoreleasePoolPage *child:指向子節點,最後一個child節點爲nil
  • uint32_t const depth:表明深度,從0開始,日後遞增
  • uint32_t hiwat:表明 high water Mark 最大入棧數量標記

話外

  • 第一頁AutoreleasePoolPage最多能放504個對象指針+一個特殊指針(邊界)
  • 以後的AutoreleasePoolPage能放505個對象指針

結構圖

對象自動釋放探究

示例代碼

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
    }
    return 0;
}
複製代碼

轉成cpp文件查看

clang -rewrite-objc main.m -o mian.cpp
複製代碼

mian.cpp。這裏只展現部分重點代碼。

struct __AtAutoreleasePool {
  //構造函數
  __AtAutoreleasePool() {
     atautoreleasepoolobj = objc_autoreleasePoolPush();
  }
  //析構函數
  ~__AtAutoreleasePool() {
     objc_autoreleasePoolPop(atautoreleasepoolobj);
  }
  void * atautoreleasepoolobj;
};

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { 
    __AtAutoreleasePool __autoreleasepool; //__AtAutoreleasePool實例化

    }
    return 0;
}
複製代碼

能夠看到原來的代碼被自動加上了__AtAutoreleasePool實例化代碼,而且調用了構造和析構函數。

atautoreleasepoolobj = objc_autoreleasePoolPush();

這是個構造函數,繼續查看源碼

//返回一個 AutoreleasePoolPage 對象
void *
objc_autoreleasePoolPush(void) {
    return AutoreleasePoolPage::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;
    }

######### autoreleaseFast()
static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        //是否有hotPage 且沒有滿
        if (page && !page->full()) {
            //沒滿就添加
            return page->add(obj);
        } else if (page) {//有hotPage,但滿了
            滿了就開分頁;
            return autoreleaseFullPage(obj, page);
        } else {//沒有hotPage,內部也是新的page
            return autoreleaseNoPage(obj);
        }
    }

######### add()
id *add(id obj)
    {
        ASSERT(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;//將next指針指向當前對象指針
        protect();
        return ret;
    }    
    
######### 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);

        do {//內部遞歸尋找最後一頁(判斷是否有page->child),找到後開新的page
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());
        //將新page置爲HotPage
        setHotPage(page);
        return page->add(obj);
    }
複製代碼

push簡單總結

  • 當對象指針被放入pool時會先判斷是否有hotPage
  • 有就添加,並將next指針指向當前對象
  • 沒有或者hotPage滿了就建立新頁再添加。建立新頁時會給depth ++ 深度增長

objc_autoreleasePoolPop(atautoreleasepoolobj);

這裏的ctxt 實際上是AutoreleasePoolPage
void objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

############ pop()
static inline void
    pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;//用於保存釋放中止的標記
        //判斷 token(對象指針)是不是空的標識指針,是的話就表明沒有對象被放入這個池子裏
        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
            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);
        }
        //開始pop
        return popPage<false>(token, page, stop);
    }

######## popPage()
static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        
        if (allowDebug && PrintPoolHiwat) printHiwat();
        //內部進行對象release
        page->releaseUntil(stop);
        
        // 殺page,即刪除空的child page,
        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();
            }
        }
    }

######### releaseUntil() //釋放對象操做
    void releaseUntil(id *stop) 
    {
        // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage //循環遍歷,直到stop對象 while (this->next != stop) { // Restart from hotPage() every time, in case -release // autoreleased more objects AutoreleasePoolPage *page = hotPage(); //若是page空了,就拿parent page while (page->empty()) { page = page->parent; setHotPage(page); } page->unprotect(); //next 指針 -- id obj = *--page->next; memset((void*)page->next, SCRIBBLE, sizeof(*page->next)); page->protect(); //不等於邊界指針 if (obj != POOL_BOUNDARY) { //對象s釋放 objc_release(obj); } } //將當前page設置爲HotPage 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簡單總結

  • 當要pop對象的時候,系統會給一個token對象指針,這個指針用於指定釋放的程度(位置)
  • 找到token對象所在的page,並生成一個stop中止對象,而後開始pop操做
  • page->releaseUntil(stop):e,內部循環遍歷執行對象的releas,直到stop對象,並將當前page設爲hotpage
  • 將已經釋放對象page殺了,即刪除空的child page

autorelease

//再來看看在ARC環境下,這個被隱藏的autorelease()方法作了什麼

- (id)autorelease {
    //將self放到_objc_rootAutorelease()
    return _objc_rootAutorelease(self);
}

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

id objc_object::rootAutorelease() {
    //TaggedPointer 不須要管理引用計數
    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());
        //仍是會調用autoreleaseFast()方法
        id *dest __unused = autoreleaseFast(obj);
        ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }

複製代碼

autorelease簡單總結

autorelease方法其實實現和push方法同樣,不一樣的是在push前會判斷對象是否能夠是TaggedPointer對象;

面試題

1.線程和autoreleasePool的關係?

  • 一個線程只有一個autoreleasePool
  • autoreleasePool嵌套時,只會建立一個page,可是有兩個池邊界

timer

相關文章
相關標籤/搜索