MJiOS底層筆記--內存管理

本文屬筆記性質,主要針對本身理解不太透徹的地方進行記錄。

推薦系統直接學習小碼哥iOS底層原理班---MJ老師的課確實不錯,強推一波。ios


別的


CADisplayLink與NSTimer

CADisplayLink(保證調用頻率和屏幕的刷幀頻率一致,60FPS(60次/s))、NSTimer會對target產生強引用,若是target又對它們產生強引用,那麼就會引起循環引用c++

target致使循環引用

以下代碼是釋放不掉的bash

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 保證調用頻率和屏幕的刷幀頻率一致,60FPS
    self.link = [CADisplayLink displayLinkWithTarget:[MJProxy proxyWithTarget:self] selector:@selector(linkTest)];
    [self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
}


- (void)linkTest
{
    NSLog(@"%s", __func__);
}
複製代碼

__weak爲何解決target的強引用

block是捕獲變量,timer是傳遞參數。ide

  1. block在捕獲變量時根據變量類型自行進行若引用處理。
  2. timer做爲參數傳遞時,內部接收到的都是對象的地址值,沒法獲取引用類型。

不過若是是NSTimer的block版本用__weak是能夠的函數

中間代理

  1. 用代理隔離self與timer
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
複製代碼
  1. 用消息轉發將selector發送回self
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return self.target;
}
複製代碼

其餘方式釋放timer

好比removeSuperView時之類吧oop

NSObject與NSProxy

NSProxy專門用來作消息轉發佈局

  1. 消息轉發速度快

NSProxy在本類沒有該方法的狀況下會直接進入消息轉發(methodSignatureForSelector:) 與 (forwardInvocation:),並不會去查找父類,動態方法解析等等。學習

  1. 大部分方法都能正確轉發

原生方法若是未主動實現,內部直接進入消息轉發。好比class,isKindOfClass等等優化

GCD定時器

GCD的定時器會更加準時ui

NSTimer依賴於RunLoop,若是RunLoop的任務過於繁重,可能會致使NSTimer不許時

而GCD定時器依賴於系統內核,並不依賴Runloop


內存佈局


Tagged Pointer

從64bit開始,iOS引入了Tagged Pointer技術,用於優化NSNumber、NSDate、NSString等小對象的存儲

  1. 在沒有使用Tagged Pointer以前,NSNumber與正常的OC對象同樣:

    須要動態分配內存、維護引用計數等,NSNumber指針存儲的是堆中NSNumber對象的地址值。

  2. 使用Tagged Pointer以後,NSNumber指針裏面存儲的數據變成了:Tag + Data,也就是將數據直接存儲在了指針中

  1. 當指針不夠存儲數據時,纔會使用動態分配內存的方式來存儲數據

  2. objc_msgSend能識別Tagged

  3. Pointer,好比NSNumber的intValue方法,直接從指針提取數據,節省了之前的調用開銷

  4. 如何判斷一個指針是否爲Tagged Pointer? class

    iOS平臺,最高有效位是1(第64bit)

    Mac平臺,最低有效位是1(16進制下爲7)

    一般來說,判斷最後一位不是0便可

NSLog(@"Person實例的內存地址=%p---指針變量p的內存地址=%p---指針變量p保存的內存地址=%p", p, &p, p);
複製代碼

MRC的內存管理

  1. 在iOS中,使用引用計數來管理OC對象的內存
  2. 一個新建立的OC對象引用計數默認是1,當引用計數減爲0,OC對象就會銷燬,釋放其佔用的內存空間
  3. 調用retain會讓OC對象的引用計數+1,調用release會讓OC對象的引用計數-1

內存管理的經驗總結

  1. 當調用alloc、new、copy、mutableCopy方法返回了一個對象,在不須要這個對象時,要調用release或者autorelease來釋放它
  2. 想擁有某個對象,就讓它的引用計數+1;不想再擁有某個對象,就讓它的引用計數-1

MRC

使用return關鍵字只會管理setget方法中的內存,dealloc中仍然須要本身釋放。


copy和mutableCopy


引用計數

  1. 在64bit中,引用計數能夠直接存儲在優化過的isa指針中
  2. 若是引用計數過大,isa中改成1而且將計數存儲到SideTable中

SideTable

一個全局table

refcnts是一個存放着對象引用計數的散列表 weak_table存放着若引用的指針與對象


weak

當一個對象A被若引用指針持有,將會以[&A,weak指針表]的形式添加進SideTable中

當對象A被釋放,能夠根據&A查找到全部指向他的weak指針並進行釋放

- (void)dealloc {
    _objc_rootDealloc(self);
}


_objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}

objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  //新isa指針
                 !isa.weakly_referenced  &&   //查看該對象是否被若引用了
                 !isa.has_assoc  &&   //關聯對象
                 !isa.has_cxx_dtor  &&  //c++析構器
                 !isa.has_sidetable_rc)) //大額引用計數
    {
        assert(!sidetable_present());
        free(this); //直接釋放
    } 
    else {
        object_dispose((id)this); 
    }
}


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(); //將指向當前對象的弱指針置位nil
    }

    return obj;
}


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

    SideTable& table = SideTables()[this]; //得到全局的SideTable
    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();
}

複製代碼

AutoreleasePool

@autoreleasepool {
    for (int i = 0; i < 1000; i++) {
        MJPerson *person = [[[MJPerson alloc] init] autorelease];
    }
}
複製代碼

cpp中

{ 
    __AtAutoreleasePool __autoreleasepool; //結構體變量
    MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}


 struct __AtAutoreleasePool {
    __AtAutoreleasePool() { // 構造函數,在建立結構體的時候調用
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
 
    ~__AtAutoreleasePool() { // 析構函數,在結構體銷燬的時候調用
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
 
    void * atautoreleasepoolobj;
 };
複製代碼

因此本質上就等於

atautoreleasepoolobj = objc_autoreleasePoolPush(); //建立釋放池

MJPerson *person = [[[MJPerson alloc] init] autorelease];

objc_autoreleasePoolPop(atautoreleasepoolobj); //釋放釋放池
複製代碼

AutoreleasePool的結構

每一個AutoreleasePoolPage對象佔用4096字節內存,除了用來存放它內部的成員變量,剩下的空間用來存放autorelease對象的地址

push,pop,autorelease

  1. 在調用objc_autoreleasePoolPush()時,插入POLL_BOUNDARY並返回地址0x1038

  2. 每一個調用autorelease的對象都會被插入到AutoreleasePoolPage中

  3. 在調用objc_autoreleasePoolPop(0x1038)時,從當前位置到0x1038全部的對象都會被執行release操做。

能夠經過如下私有函數來查看自動釋放池的狀況

extern void _objc_autoreleasePoolPrint(void);
複製代碼

AutoreleasePool的維護

  1. 始終有一個被標記hotPage的活躍AutoreleasePoolPage被系統持有
  2. page之間經過雙向鏈表連接
  3. 若是push/autorelease操做時當前page已滿,將會建立一個page或跳轉到下一個page

runloop與AutoreleasePool

iOS在主線程的Runloop中註冊了2個Observer,監聽了三個狀態。並適時操做AutoreleasePool

  1. 第1個Observer監聽了kCFRunLoopEntry事件

    在進入runloop時,會調用objc_autoreleasePoolPush()

  2. 第2個Observer監聽了kCFRunLoopBeforeExit事件

    在退出runloop時,會調用objc_autoreleasePoolPop()

  3. 第2個Observer還監聽了kCFRunLoopBeforeWaiting事件

    在當前循環結束,準備休眠時時,會調用objc_autoreleasePoolPop()隨後再調用一次objc_autoreleasePoolPush()


autorelease對象

借用羣裏一位大佬的解釋

通常除了init其餘基本上都是autorelease的,包括C函數返回對象

也就是說init方法放回的對象,默認是會被retain/release,而其餘的對象默認會autorelease

很顯然的,兩者的釋放時機不一樣,因此纔會有以下狀況發生。

相關文章
相關標籤/搜索