iOS的記憶體管理

2021-07-10 09:40:48 字數 3785 閱讀 1353

今晚有空,總結一下學習ios記憶體管理的一些認識。

文章中可能會涉及一些相對底層的知識,c的記憶體管理知識,慎入。

前方高能!

前方高能!!

前方高能!!!

經典的記憶體劃分:棧、堆、bss段、資料段、**段。如

下圖 :

先說堆疊:在c語言裡,可以簡單的說malloc等方法主動申請記憶體,其記憶體空間是在堆上,其他的在棧上。

展開一點說,臨時變數是在棧上的,一般常用的指標也是存放在棧上的,而指標指向的內容在堆上。這麼說可能不是很好理解。乙個簡單的例子:

int *foo = (int *)malloc(sizeof(int)*10);
這裡我們申請了10個int長度(40byte)的記憶體空間,指向這40byte位址的指標foo存放在棧上,而這40byte記憶體位址是堆上的。

堆和棧的生長方向也是不一樣的,棧是向下生長的,堆是向上生長的,二者是乙個閉合的區間。如果你能記住上面的圖,這個知識點就很容易記住了。如果棧向上生長,就溢位了→_→

記憶體管理手段也是不同的,堆是程式設計師手動管理記憶體,棧是系統自動管理(有木有mrc和arc的即視感(≧▽≦)/)。

為什麼要劃分出堆和棧來呢,這個不是很好說的明白,你只要知道棧是一級快取,堆是二級快取,棧的讀寫速度快於堆就ok了。這裡有一丟丟類似於記憶體和硬碟的區別(實際上不是)。

關於堆疊溢位,我會單獨列另一篇部落格來講,此處保持神秘感。

撒花,♪(^∇^*)。

oc中說道記憶體管理,必然繞不開的就是引用計數技術,這也是很多面試官非常熱衷的話題。那麼什麼是引用技術呢。

首先,要澄清一點,引用計數不是oc首創的,在遠古的c時代,就有這麼個東西了。而oc在物件導向程式設計上封裝了該技術。

下面進入正題。狹義說的記憶體管理特製堆記憶體管理,就是我們常說的需要程式設計師手動管理的記憶體。在c語言中,對於一塊已申請的記憶體,當我們不再需要使用時,需要手動的free掉這塊記憶體。否則當函式的生命週期結束時,就會發生記憶體洩露。記憶體洩露怎麼發生的呢。如下:

(void) foo

前方高能:下面是一些冷門c知識,無感的同學請跳過。

程式對記憶體的使用是這樣的,圈一塊地(記憶體),宣告使用權,這塊地是我的(引用flag),別人不能動。當別人要用地時,發現這裡有個flag,就不會去動他。當不需要使用這塊地時,只需要把flag拔掉,就ok了。

根據函式內臨時變數生命週期,我們知道,當函式執行結束時,會釋放掉bar,這裡釋放的是儲存bar指標的棧空間,這是系統自動執行的。這時一件恐怖的事情發生了,圈地的flag還插著,但是我們已經失去了對flag的控制權(flag所在的棧位址已被釋放),之後任何人都無法動這塊圈好的地了,包括我自己。此時就出現了記憶體洩露。

下面回到oc。引用技術的原理,就是對每塊已經申請的記憶體設計乙個計數器(初始為1),當有其他人想用我們申請的這塊記憶體時,就讓引用計數加1,當他不用的時候就減1。當引用計數器為1時,若又有人說我不用這塊位址了,就說明再也沒人需要用了,這塊記憶體可以釋放掉。

這裡有乙個小知識,引用計數為1時,再release不會減到0,而是會直接釋放掉,這涉及到機器執行效率的問題。為了說話方便,我們一般會說減到0。

在c語言的記憶體管理上,有兩種行為,一種是「誰申請誰釋放」,就是說記憶體的釋放必須由擁有者釋放,其他引用都是紙老虎。一種是「誰擁有誰釋放」,規定只要你引用了這塊位址,都可以做釋放操作。孰優孰劣難以道清,只能說oc其實是偏向於誰擁有誰釋放的。

終於告別c了,進入oc記憶體管理,很多同學可以松一口氣了。mrc,oc中的手動記憶體管理。相較於c的free,進行手動記憶體管理,mrc其實只是相當於物件導向的手動記憶體管理。mrc把物件的記憶體管理封裝了一下,讓你可以不去理物件記憶體的生命週期。但是總的來說還是很麻煩的。

用一下你就會發現,mrc和c手動記憶體管理都是走的同乙個路子,不然為啥二者名字辣麼像捏。

後來人們發現,大多數情況下,乙個物件只會有乙個持有者,不會有很多次引用。懶人推動了科技進步,於是有了自動釋放池。

自動釋放池又是個神馬鬼?就是讓你把物件丟到這個大池子裡,當生命週期結束時,他會自動幫池子裡的物件引用計數減1。注意,這裡是減1,乙個pool只能減1,而不是乙個遍歷,減到0;

有木有覺得mrc仍然***啊(我不會告訴你我連線程鎖都自己封過→_→)。

對於這種***的東西,必須堅決乾掉。世界是屬於懶人的。所以有了arc。

牆裂建議使用arc,對於mrc專案也建議逐漸重構改為arc。arc基本做到了不需要太操心記憶體問題,程式簡潔幹練很多。需要說明的是,無論自動釋放池還是arc都是一種編譯時行為,也就是說,是編譯器手動加上了release,再手動改為free這樣乙個過程。而不是執行時動態調整的。

arc下貌似沒什麼好講的,但是程式設計師的雙手解放了,總得在桌子下面做點啥吧。

arc裡面有乙個很關鍵的概念就是強引用,若引用。

這是個什麼概念呢,簡單講,就是強引用引用計數加1,弱引用引用計數不變。這會造成乙個後果,就是弱引用對記憶體釋放沒有發言權。這種後果是良性的,在某些特殊場景下灰常好用。比如:

#import 

#import "dog.h"

@inte***ce

person : nsobject

@property (nonatomic, strong) dog *dog;

@end

#import 

#import "person.h"

@inte***ce

dog : nsobject

@property (nonatomic, strong)person *person;

@end

有乙個dog類,有乙個主人person,有乙個person類有乙個寵物dog。二者相互引用,就造成了乙個死迴圈,導致二者無法正常釋放記憶體,這裡就用到了弱引用,將二者中任意乙個屬性設為weak,就可以解除該迴圈引用。

在開發中,會經常遇到可變陣列、不可變陣列,可變字串、不可變字串。簡單寫乙個:

char *str = "hello world";

nslog(@"字串常量\t---> %p", str);

nsstring *string = @"hello world";

nslog(@"字串\t\t---> %p",string);

nsmutablestring *mutstring = [nsmutablestring stringwithstring:@"hello world"];

nslog(@"可變字串\t---> %p", mutstring);

以及執行結果

字串常量--

-> 0x10436765b

字串---> 0x104368088

可變字串--

-> 0x7f991159e2c0

這裡為了清楚,將部分執行結果裁掉了。從結果上看可以看到,string和mutstring明顯不在同乙個記憶體區域。有興趣的童鞋可以看一下nsarray和nsmutablearray,有驚喜。

深拷貝和淺拷貝,分別對應著值傳遞和址傳遞。

這裡比較容易引起爭議的是copy和mutablecopy。

對於不可變物件,copy是淺拷貝,mutablecopy是深拷貝。

對於可變物件,二者都是深拷貝。

為毛會醬紫呢。改天單拉出來一章嘮嘮吧。平時只需要記住上面那句口訣就ok了。

iOS的記憶體管理

通過字面的方式建立出來的物件儲存在常量區,通過物件方法和類方法建立出來的物件儲存在堆區 記憶體有系統管理,區域性變數儲存在棧,當變數離開其所在 快就會被 堆記憶體需要程式設計師自己管理,oc中的物件儲存在堆中 記憶體管理不當會造成的兩個問題 記憶體洩露,不再需要的物件沒有釋放,導致記憶體洩露,記憶體...

ios記憶體管理

引用計數 每個物件有乙個與之相關的整數,稱作 引用計數器 或者 保留計數器 當某段 需要訪問乙個物件時,該段 會將物件的保留計數器 1,表示需要訪問這個物件 當結束對該物件的訪問時,保留計數器 1,表示它不在訪問該物件 當保留計數器為0時,物件被銷毀,所佔記憶體被系統收回。當使用new retain...

iOS記憶體管理

前提 1 以下是針對cocoa物件,不包括core foundation 2 cocoa物件都是用引用計數來跟蹤物件的記憶體使用情況的。3 在子類裡面父類先初始化和後釋放的原則。自己想下為什麼 棧空間和堆空間的區別。我們說的記憶體管理都是基於堆空間的,因為函式內的棧空間是由編譯器自己控制的。關於co...