heap(堆)和stack(棧)是內(nèi)存管理的兩個(gè)重要概念今瀑。在這里我們指的不是數(shù)據(jù)結(jié)構(gòu)上面的堆與棧,在這里指的是內(nèi)存的分配區(qū)域欢唾。
了解堆、棧脚作,有助于更好地理解參數(shù)傳遞葫哗,多態(tài)缔刹,線程球涛,異常和垃圾收集。
棧(堆棧/stack)
stack的空間由操作系統(tǒng)進(jìn)行分配校镐,分配發(fā)生在連續(xù)的內(nèi)存塊上亿扁,向低內(nèi)存地址堆擴(kuò)展的
在現(xiàn)代操作系統(tǒng)中,一個(gè)線程會分配一個(gè)stack鸟廓,當(dāng)線程退出堆棧時(shí)回收. 當(dāng)一個(gè)函數(shù)被調(diào)用,一個(gè)stack frame(棧幀)就會被壓到stack里从祝。里面包含這個(gè)函數(shù)涉及的參數(shù),局部變量,返回地址等相關(guān)信息襟己。當(dāng)函數(shù)返回后,這個(gè)棧幀就會被銷毀。而這一切都是自動的,由系統(tǒng)幫我們進(jìn)行分配與銷毀牍陌。對于程序員是透明的,我們不需要手動調(diào)度擎浴。
堆(heap)
heap的空間需要手動分配,堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu)
heap與動態(tài)內(nèi)存分配相關(guān),內(nèi)存可以隨時(shí)在堆中分配和銷毀毒涧。我們需要明確請求內(nèi)存分配與內(nèi)存銷毀贮预。 簡單來說,就是malloc與free契讲。應(yīng)用程序通常只有一個(gè)堆仿吞。
內(nèi)存不足問題更可能發(fā)生在堆棧中,而堆內(nèi)存中的主要問題是碎片和內(nèi)存泄漏問題
棧 VS 堆
堆棧更快捡偏,因?yàn)樵L問模式使得從中分配和釋放內(nèi)存變得微不足道(指針/整數(shù)簡單地遞增或遞減)唤冈,而堆在分配或釋放中涉及更復(fù)雜的簿記。此外银伟,堆棧中的每個(gè)字節(jié)都經(jīng)常被頻繁地重用你虹,這意味著它往往被映射到處理器的緩存,使其非惩埽快售葡。堆的另一個(gè)性能損失是堆(主要是全局資源)通常必須是多線程安全的,即每個(gè)分配和釋放需要 - 通常 - 與程序中的“所有”其他堆訪問同步忠藤。
Objective-C中的Stack和Heap
首先所有的Objective-C對象都是分配在heap的挟伙。 在OC最典型的內(nèi)存分配與初始化就是這樣的。
NSObject *obj = [[NSObject alloc] init];
一個(gè)對象在alloc的時(shí)候模孩,就在Heap分配了內(nèi)存空間尖阔。
stack對象通常有速度的優(yōu)勢,而且不會發(fā)生內(nèi)存泄露問題榨咐。那么為什么OC的對象都是分配在heap的呢介却? 原因在于:
- stack對象的生命周期所導(dǎo)致的問題。例如一旦函數(shù)返回块茁,則所在的stack frame就會被摧毀齿坷。那么此時(shí)返回的對象也會一并摧毀。這個(gè)時(shí)候我們?nèi)etain這個(gè)對象是無效的数焊。因?yàn)檎麄€(gè)stack frame都已經(jīng)被摧毀了永淌。簡單而言,就是stack對象的生命周期不適合Objective-C的引用計(jì)數(shù)內(nèi)存管理方法佩耳。
- stack對象不夠靈活遂蛀,不具備足夠的擴(kuò)展性。創(chuàng)建時(shí)長度已經(jīng)是固定的,而stack對象的擁有者也就是所在的stack frame
關(guān)于Block
為什么block需要使用copy修飾符干厚?
block在創(chuàng)建時(shí)是stack對象,如果我們需要在離開當(dāng)前函數(shù)仍能夠使用我們創(chuàng)建的block李滴。我們就需要把它拷貝到堆上以便進(jìn)行以引用計(jì)數(shù)為基礎(chǔ)的內(nèi)存管理螃宙。
最終得到的答案是這與block對象在創(chuàng)建時(shí)是stack對象有關(guān)。
所以,其實(shí)Objective-C是有它的Stack object的所坯。是的,那就是block.
在Objective-C語言中谆扎,一共有3種類型的block:
- _NSConcreteGlobalBlock 全局的靜態(tài)block,不會訪問任何外部變量芹助。
- _NSConcreteStackBlock 保存在棧中的block燕酷,當(dāng)函數(shù)返回時(shí)會被銷毀。
- _NSConcreteMallocBlock 保存在堆中的block周瞎,當(dāng)引用計(jì)數(shù)為0時(shí)會被銷毀苗缩。
這里我們主要基于內(nèi)存管理的角度對它們進(jìn)行分類。
- NSConcreteGlobalBlock,這種不捕捉外界變量的block是不需要內(nèi)存管理的,這種block不存在于Heap或是Stack而是作為代碼片段存在,類似于C函數(shù)声诸。
- NSConcreteStackBlock酱讶。這就是這次探索的重點(diǎn)了,需要涉及到外界變量的block在創(chuàng)建的時(shí)候是在stack上面分配空間的,也就是一旦所在函數(shù)返回,則會被摧毀。這就導(dǎo)致內(nèi)存管理的問題,如果我們希望保存這個(gè)block或者是返回它,如果沒有做進(jìn)一步的copy處理,則必然會出現(xiàn)問題彼乌。
- NSConcreteMallocBlock,因此為了解決block作為Stack object的這個(gè)問題,我們最終需要把它拷貝到堆上面來泻肯。而此時(shí)NSConcreteMallocBlock扮演的就是這個(gè)角色。
拷貝到堆后,block的生命周期就與一般的OC對象一樣了,我們通過引用計(jì)數(shù)來對其進(jìn)行內(nèi)存管理慰照。
ARC
在ARC情況下,創(chuàng)建的block仍然是NSConcreteStackBlock類型,只不過當(dāng)block被引用或返回時(shí),ARC幫助我們完成了copy和內(nèi)存管理的工作灶挟。