這篇讀書筆記主要介紹了C語言內存分配、block疑難點、property的深刻理解,本身對這三塊作了系統性的總結,但願對你有所幫助。html
C語言內存分配git
Objective-C從名字來看就能夠知道是一門超C語言,因此瞭解C語言的內存模型對於理解Objective-C的內存管理有很大的幫助。C語言內存模型圖以下:程序員
1-1 C內存分配.pnggithub
從圖中能夠看出內存被分紅了5個區,每一個區存儲的內容以下:api
棧區(stack):存放函數的參數值、局部變量的值等,由編譯器自動分配釋放,一般在函數執行結束後就釋放了,其操做方式相似數據結構中的棧。棧內存分配運算內置於處理器的指令集,效率很高,可是分配的內存容量有限,好比iOS中棧區的大小是2M。數據結構
堆區(heap):就是經過new、malloc、realloc分配的內存塊,它們的釋放編譯器不去管,由咱們的應用程序去釋放。若是應用程序沒有釋放掉,操做系統會自動回收。分配方式相似於鏈表。併發
靜態區:全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量和未初始化的靜態變量在相鄰的另外一塊區域。程序結束後,由系統釋放。異步
常量區:常量存儲在這裏,不容許修改的。函數
代碼區:存放函數體的二進制代碼。動畫
棧區在何時釋放內存呢?咱們經過下面的一個例子來講明下:
1 2 3 4 5 |
|
在上面的代碼中當程序執行到 } 的時候,變量i和j的做用域已經結束了,編譯器就會自動釋放掉i和j所佔的內存,因此理解好做用域就理解了棧區的內存分配。
棧區和堆區的區別主要爲如下幾點:
對於棧來講,內存管理由編譯器自動分配釋放;對於堆來講,釋放工做由程序員控制。
棧的空間大小比堆小許多。
棧是機器系統提供的數據結構,計算機會在底層對棧提供支持,因此分配效率比堆高。
棧中存儲的變量出了做用域就無效了,而堆因爲是由程序員進行控制釋放的,變量的生命週期能夠延長。
參考文章:
block
聲明block屬性的時候爲何用copy呢?
在說明爲何要用copy前,先思考下block是存儲在棧區仍是堆區呢?其實block有3種類型:
全局塊(_NSConcreteGlobalBlock)
棧塊(_NSConcreteStackBlock)
堆塊(_NSConcreteMallocBlock)
全局塊存儲在靜態區(也叫全局區),至關於Objective-C中的單例;棧塊存儲在棧區,超出做用域則立刻被銷燬。堆塊存儲在堆區中,是一個帶引用計數的對象,須要自行管理其內存。
怎麼判斷一個block所在的存儲位置呢?
block不訪問外界變量(包括棧中和堆中的變量)
block既不在棧中也不在堆中,此時就爲全局塊,ARC和MRC下都是如此。
block訪問外界變量
MRC環境下:訪問外界變量的block默認存儲在棧區。
ARC環境下:訪問外界變量的block默認存放在堆中,其實是先放在棧區,在ARC狀況下自動又拷貝到堆區,自動釋放。
使用copy修飾符的做用就是將block從棧區拷貝到堆區,爲何要這麼作呢?咱們看下Apple官方文檔給出的答案:
1-2 block copy.png
經過官方文檔能夠看出,複製到堆區的主要目的就是保存block的狀態,延長其生命週期。由於block若是在棧上的話,其所屬的變量做用域結束,該block就被釋放掉,block中的__block變量也同時被釋放掉。爲了解決棧塊在其變量做用域結束以後被釋放掉的問題,咱們就須要把block複製到堆中。
不一樣類型的block使用copy方法的效果也不同,以下所示:
block的類型 存儲區域 複製效果
_NSConcreteStackBlock 棧 從棧複製到堆
_NSConcreteGlobalBlock 靜態區(全局區) 什麼也不作
_NSConcreteMallocBlock 堆 引用計數增長
加上__block以後爲何就能夠修改block外面的變量了?
咱們先看下例子1:
1 2 3 4 5 6 7 8 9 10 11 |
|
運行後輸出的結果以下:
1 |
|
爲何不是50呢?這個問題稍後作解釋。咱們在看加入__block的狀況,例子2以下:
1 2 3 4 5 6 7 8 9 10 11 |
|
運行後輸出的結果以下:
1 |
|
兩次運行結果不同,中間發生了什麼呢?咱們接下來來具體分析下。
在例子1中,block會把anInteger變量複製爲本身私有的const變量,也就是說block會捕獲棧上的變量(或指針),將其複製爲本身私有的const變量。在例子1中,在進行anInteger = 50的操做的時候,block已經將其複製爲本身的私有變量,因此這裏的修改對block裏面的anInteger不會形成任何影響。
在例子2中,anInteger是一個局部變量,存儲在棧區的。給anInteger加入__block修飾符所起到的做用就是隻要觀察到該變量被block所持有,就將該變量在棧中的內存地址放到堆中,此時無論block外部仍是內部anInterger的內存地址都是同樣的,進而無論在block外部仍是內部均可以修改anInterger變量的值,因此anInteger = 50以後,在block輸出的值就是50了。能夠經過一個圖簡單來描述一下:
1-3 __block.png
block中循環引用的問題?使用系統的block api是否也考慮循環引用的問題?weak與strong之間的區別?
在使用block的時候,咱們要特別注意循環引用的問題,先來看一個循環引用的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
在上面的代碼中咱們聲明瞭一個block屬性,因此self對block有一個強引用。而在block內部又對self進行了一次強引用,這樣就造成了一個封閉的環,也就是咱們常常說的強引用循環。引用關係如圖:
1-4 strong retain cycle.png
在這種狀況下,因爲其相互引用,內存不可以進行釋放,就形成了內存泄漏的問題。怎麼解決循環引用的問題呢?咱們常常經過聲明一個weakSelf來解決循環引用的問題,更改後的代碼以下:
1 2 3 4 5 6 |
|
加入weakSelf以後,block對self就由強引用關係變成了弱引用關係,這樣在屬性所指的對象遭到摧毀時,屬性值也會被清空,就打破了block捕獲的做用域帶來的循環引用。這跟設置self.block = nil是一樣的道理。
在使用系統提供的block api須要考慮循環引用的問題嗎?好比:
1 2 3 |
|
在這種狀況下是不須要考慮循環引用的,由於這裏只有block對self進行了一次強引用,屬於單向的強引用,沒有造成循環引用。
weak與strong有什麼區別呢?先看一段代碼:
1 2 3 4 5 6 7 8 |
|
這段代碼看起來很正常呀,可是在併發執行的時候,block的執行是能夠搶佔的,並且對weakSelf指針的調用時序不一樣能夠致使不一樣的結果,好比在一個特定的時序下weakSelf可能會變成nil,這個時候在執行doAnotherThing就會形成程序的崩潰。爲了不出現這樣的問題,採用__strong的方式來進行避免,更改後的代碼以下:
1 2 3 4 5 6 7 8 9 |
|
從代碼中能夠看出加入__strong所起的做用就是在搶佔的時候strongSelf仍是非nil的,避免出現nil的狀況。
總結:
在block不是做爲一個property的時候,能夠在block裏面直接使用self,好比UIView的animation動畫block。
當block被聲明爲一個property的時候,須要在block裏面使用weakSelf,來解決循環引用的問題。
當和併發執行相關的時候,當涉及異步的服務的時候,block能夠在以後被執行,而且不會發生關於self是否存在的問題。
參考文章:
property
@synthesize和@dynamic分別有什麼做用?
在說二者分別有什麼做用前,咱們先看下@property的本質是什麼:
1 |
|
從上面能夠看出@property的本質就是ivar(實例變量)加存取方法(getter + setter)。在咱們屬性定義完成後,編譯器會自動生成該屬性的getter和setter方法,這個過程就叫作自動合成。除了生成getter與setter方法,編譯器還要自動向類中添加適當類型的實例變量,而且在屬性名前面加下劃線,以此作實例變量的名字。
@synthesize的做用就是若是你沒有手動實現getter與setter方法,那麼編譯器就會自動爲你加上這兩個方法。
@dynamic的做用就是告訴編譯器,getter與setter方法由用戶本身實現,不自動生成。固然對於readonly的屬性只須要提供getter便可。
若是都沒有寫@synthesize和@dynamic,那麼默認的就是@synthesize var = _var;
爲了加深對@synthesize和@dynamic的理解,咱們來看幾個具體的例子,例子1代碼以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
咱們在進行編譯的時候沒有問題,可是運行的時候就會發生崩潰,崩潰的緣由以下:
1 |
|
崩潰的緣由是不識別setName方法,這也驗證了若是加入了@dynamic的話,編譯系統就不會本身生成getter和setter方法了,須要咱們本身來實現。
咱們在來看下@synthesize合成實例變量的規則是什麼?例子2代碼以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
從代碼中能夠看出,一、當咱們指定了成員變量的名稱(指定爲帶下劃線的myName),就會生成指定的成員變量。若是代碼中存在帶下劃線的name,就不會在生成了。二、若是是@synthesize name;還會生成一個名稱爲帶下劃線的name成員變量,也就是說若是沒有指定成員變量的名稱會自動生成一個屬性同名的成員變量。三、若是是@synthesize name = _name; 就不會生成成員變量了。
在有了自動合成屬性實例變量以後,@synthesize還有哪些使用場景呢?先搞清楚一個問題,何時不會使用自動合成?
同時重寫了setter和getter時。
重寫了只讀屬性的getter時。
使用了@dynamic時。
在@protocol中定義的全部屬性。
在category中定義的全部屬性。
重載的屬性。
注意點:
在category中使用@property也是隻會生成getter和setter方法的聲明,若是真的須要給category增長屬性的實現,須要藉助於運行時的兩個函數:objc_setAssociatedObject和objc_getAssociatedObject。
在protocol中使用property只會生成setter和getter方法聲明,使用屬性的目的是但願遵照我協議的對象可以實現該屬性。
weak、copy、strong、assgin分別用在什麼地方?
什麼狀況下會使用weak關鍵字?
在ARC中,出現循環引用的時候,會使用weak關鍵字。
自身已經對它進行了一次強引用,沒有必要再強調引用一次。
assgin適用於基本的數據類型,好比NSInteger、BOOL等。
NSString、NSArray、NSDictionary等常用copy關鍵字,是由於他們有對應的可變類型:NSMutableString、NSMutableArray、NSMutableDictionary;
除了上面的三種狀況,剩下的就使用strong來進行修飾。
爲何NSString、NSDictionary、NSArray要使用copy修飾符呢?
要搞清楚這個問題,咱們先來弄明白深拷貝與淺拷貝的區別,以非集合類與集合類兩種狀況來進行說明下,先看非集合類的狀況,代碼以下:
1 2 3 |
|
運行以後,輸出的信息以下:
1 |
|
能夠看出複製事後,內存地址是同樣的,沒有發生變化,這就是淺複製,只是把指針地址複製了一份。咱們改下代碼改爲[name mutableCopy],此時日誌的輸出信息以下:
1 |
|
咱們看到內存地址發生了變化,而且newName的內存地址的偏移量比name的內存地址要大許多,因而可知通過mutableCopy操做以後,複製到堆區了,這就是深複製了,深複製就是內容也就進行了拷貝。
上面的都是不可變對象,在看下可變對象的狀況,代碼以下:
1 2 3 |
|
運行以後日誌輸出信息以下:
1 |
|
從上面能夠看出copy以後,內存地址不同,且都存儲在堆區了,這是深複製,內容也就進行拷貝。在把代碼改爲[name mutableCopy],此時日誌的輸出信息以下:
1 |
|
能夠看出可變對象copy與mutableCopy的效果是同樣的,都是深拷貝。
總結:對於非集合類對象的copy操做以下:
[immutableObject copy]; //淺複製
[immutableObject mutableCopy]; //深複製
[mutableObject copy]; //深複製
[mutableObject mutableCopy]; //深複製
採用一樣的方法能夠驗證集合類對象的copy操做以下:
[immutableObject copy]; //淺複製
[immutableObject mutableCopy]; //單層深複製
[mutableObject copy]; //深複製
[mutableObject mutableCopy]; //深複製
對於NSString、NSDictionary、NSArray等常用copy關鍵字,是由於它們有對應的可變類型:NSMutableString、NSMutableDictionary、NSMutableArray,它們之間可能進行賦值操做,爲確保對象中的字符串值不會無心間變更,應該在設置新屬性時拷貝一份。