對于OC的使用者來說,最會被問到的就是iOS開發中的內存管理。而只要涉及到內存管理,就肯定會涉及到property。而且在平常iOS開發的面試中,我們也經常會被問到相關的問題。所以這次就結合我所見到的和自己對于屬性的理解來進行簡述,希望對你們有幫助。
在講述屬性前,我們需要對于iOS開發中的內存管理有一個初步的了解。
本文中的部分內容來源于:這里和這里還有部分來源于蘋果官方文檔。
iOS開發中的內存使用情況
- 棧(stack):棧是編譯器自動分配并釋放,用來存放函數的參數,局部變量。
- 堆(heap):堆一般是程序員自己分配和釋放,如果我們在使用的過程中,沒有釋放,那么等到程序完全結束,系統將會對堆中的內容進行回收。一般開發中的alloc就是存放在堆中的
- 全局變量(靜態變量)(static):全局變量和靜態變量是單獨存放的,因為他們的聲明周期和整個程序的生命周期一致。釋放的時間是由整個程序結束后系統負責回收。
- 文字常量區:一般用來存放常量字符串,比如說
String *str = @"hello world"
,他的釋放也和全局變量一致,在程序結束后進行釋放。 - 程序代碼區:用來存放函數的二進制內容的區域。
他們之間的在內存中存放的關系如下圖所示:
而在日常開發過程中,我們所遇到最多的也是最容易問到的就是堆和棧,可能有的人對于這兩者還是很模糊,所以我再引用之前看到過的一段話來解釋(由于很久以前看到了,已經忘記了出處,如果有人能夠告訴我,我一定加上):
內管管理就像做菜,而棧(stack)的使用就像我們下館子,我們只要去酒店,告訴老板我們要什么,他們就會幫我們準備好,我們只要吃完付錢,就可以了,也不用去管理那些之后的瑣事。而堆(heap)就像我們自己做菜,什么東西都得自己買好,做什么,怎么做都得提前想好。做完之后還需要自己收拾最后的垃圾。一般我們alloc一個對象,該對象的內存就會被分配到堆上,而非對象創建的時候就會放到棧上,由系統在不需要的時候進行回收。
iOS內存管理機制
當你創建的對象的時候,你總得需要告訴操作系統,你將要在什么時候對占有這塊內存的對象進行釋放,就像平常生活中,你怎么才能確定這房間里面是有人的還是沒有人的呢?
一般來說,我們肯定會這么干,每個人進去的時候登記下,然后出來的時候再登記下,這樣只要我們在需要管理的時候看下登記再按的人數是不是為0就可以了,只要為0,那么就說明里面沒有人,否則就有人,蘋果也是這么處理內存管理的。在你創建一個對象的時候,他會需要進行retain
,然后在你不在持有他的時候進行release
,所以每個對象都有一個retain count
來進行計數。
在iOS中有2套內存管理機制:MRC(MannulReference Counting)和ARC(Automatic Reference Counting)。其中ARC起源于iOS 4.3,在那之前,蘋果開發者只能手動使用retain
和release
來進行內存管理。這樣做的問題很明顯,如果你在開發過程中,一個不小心沒有retain
和release
成對出現,那么很容易使得內存沒有釋放,最后導致程序內存不足而導致閃退。
對于ARC,蘋果的官方文檔是這么解釋的:
Automatic Reference Counting (ARC) is a compiler feature that provides automatic memory management of Objective-C objects. Rather than having to think about retain and release operations, ARC allows you to concentrate on the interesting code, the object graphs, and the relationships between objects in your application.
ARC works by adding code at compile time to ensure that objects live as long as necessary, but no longer. Conceptually, it follows the same memory management conventions as manual reference counting (described in Advanced Memory Management Programming Guide) by adding the appropriate memory management calls for you.
總的來說,主要意思就是ARC在本質上就像MRC一樣,但是它能夠讓你花費更少的時間來考慮代碼中的retain
和release
。與此同時,編譯器能夠幫你更加準確的將retain
和release
加在你代碼中真正需要的地方。
理解如下圖:

有人可能會提到GC(Garbage Collection)——垃圾回收機制。在現在的iOS中GC沒有被使用,而在MAC OS X中,GC是被使用的,不過對于GC,蘋果是這么解釋的:
Garbage collection is deprecated in OS X Mountain Lion v10.8, and will be removed in a future version of OS X. Automatic Reference Counting is the recommended replacement technology. To aid in migrating existing applications, the ARC migration tool in Xcode 4.3 and later supports migration of garbage collected OS X applications to ARC
總的來說ARC將在未來的某一天來取代GC在MAC中的位置。
property(屬性)
就算理解的再多,我們還是要和實際相結合。實際運用中與之相對應的就是實例變量的屬性。
由于 iOS 中 MRC已經太過于古老,在這就不再多提。
在ARC中,我們創建實例變量經常是這樣的
@property (atomic/nonatomic/assign/retain/strong/weak/unsafe_unretained/copy) Number* num
所以我們就為讓這內容中的這幾個property來進行解釋。
- atomic:原子性,簡單的解釋就是說,他是線程安全的,但是由于線程安全,在操作的過程中,編譯器會自己給他上鎖,解鎖。這樣會造成資源的浪費(因為我們平常開發中不會那么頻繁的考慮到線程安全這個問題)該屬性為默認值
- nonatomic:非原子性,這個和上面那個是雙子星,但是他們正好相反,這個由于不安全的,但是因為沒有了鎖的問題,這樣資源就會盡可能的被利用,所以我們在日常開發中使用這個,而對于他的安全性,我們一般都自己在后期開發的過程中,在使用多線程的過程中進行考慮。
- assign:給予了setter方法,assign一般指向一個不是指針指向的對象,比如說
CGFloat
這類值。(關于assign
和weak
在delegate中的區別將會在后面提到) - retain:
retain
用于指向一個有指針的對象,就像MRC中的retain,他是為了增加retain count
,這樣使得對象能夠在autoreleasepool
中持有內存 - strong:
strong
是在ARC中用來代替retain
的,所以原理相同,即retain count
加一 - weak:
weak
和strong
一般只要懂一點英語的就知道,他們又是一堆雙子星,strong
是表示持有,而weak
表示,只是簡單的引用。這意味著等到持有weak
的對象唄釋放的時候,weak
表示的對象也被釋放。而需要注意的是weak
釋放后他就會為nil。 - unsafe_unretained:
unsafe_unretained
一般很少被用到,主要的原因是他一般用于在Cocoa底層的那些不能支持weak
屬性的變量,比如說NSTextView
,NSFont
,NSColorSpace
等。而他和weak不同的地方是,持有weak
的對象被釋放后,weak
對象會被指向nil,而unsafe_unretained
則不會被指向為nil。這樣就有了安全隱患。 - copy:
copy
一般我們用在NSString
,NSArray
,NSDictionary
,而原因就是copy會在復制的時候講源對象進行拷貝。這樣他只想的將會是一個新的,retain count
為1的對象。這樣在我們對于這個對象的內容進行修改的時候,它將不會影響到原來的那個對象。
除了這幾種之外,我們還有4種屬性沒有提到
- getter:設置getter方法
- setter:設置setter方法
- readonly:只讀
- readwrite:可讀寫
這四種因為可以根據字面意思來進行設置在這就不進行過多的介紹。
這里再補上一句之前提到的問題:
關于為什么使用
weak
而不是用assign
來對delegate進行標注。
首先delegate一般的類型都是id,即可以指向所有對象的id
類型。所以我們既可以使用assign指向他,也可以使用weak指向他。但是因為weak在不被持有的時候會指向nil,而大家都知道,所有通過nil的方法在調用函數的時候,都會返回為nil,這樣就能夠保證了程序的穩定性,就算沒有東西返回,他還是能夠正常解析(只是解析出來的值為nil)。而如果使用assign的話,如果一不持有他。那么下次再調用它的時候,他將會指向一個不知名的地址,即野指針。這樣就會使得整個程序crash。
autoreleasepool(自動釋放池)
既然扯到release,那就不得不提下autoreleasepool,顧名思義,他就是一個用來自動幫你釋放的池子(這特么不是廢話么)。一般情況下我們不怎么會去使用它,因為在AppKit和UIKit的框架中,事情基本上默認的放在autorelease pool block
中完成的。這樣子當你完成這些內容后,對應在內存中的數據就會自動釋放,這樣就不需要你手動去處理這些數據,從而保證了程序的安全性。
當然這里也提到了這是在使用AppKit和UIKit的情況下。我們仍然有以下這幾種情況來使用autoreleasepool的情況:
- 編寫的是命令行程序,不基于UI框架
- 當你需要寫一個循環,循環里面有很多臨時變量的時候
- 當你大量使用輔助線程
總的來說autoreleasepool是為了盡可能的減少無用變量在內存中的占用情況。從而使得程序所需要的內存更少。至于autoreleasepool的釋放時間,這就要涉及到runloop,具體內容可以參見sunnyxx大神的這篇文章,相信對你肯定有很大幫助。
循環引用
循環引用對于內存來說是一個大問題。這個問題我們更多的在block中可能會碰到,不過總結起來就是這么一句話:
A對象持有B,那么B的
retain count
為1,而此時A因為被其他變量持有,所有A的retain Count
為1,而B又要引用A,那么A的retain count
又要加一,這樣等到持有A的那個對象釋放A的時候,就算A的retain count
減一,但還是為1,所以無法釋放,而因為A無法釋放,導致B也無法成功釋放,而外部沒有持有A或B中的任意一個,這樣就導致了這塊內存空間一直被持有。
圖示如下:
具體的問題我將會在解釋block的時候具體說明,需要知道的一點就是,如果存在循環引用,那么就需要把循環引用中間的一條線斷掉,從而使用weak來代替,使得當strong的一方被釋放的時候,weak也能被正常釋放。
希望以上文章能夠對你有所幫助。