1.什么是 ARC瀑焦?
ARC
是 iOS 5
引入的內存管理新功能 -- 自動引用計數(shù)
饥伊。它的工作原理大致是這樣:當我們編譯源碼時辅愿,編譯器會分析源碼中每個對象的生命周期竿报,然后基于這些對象的生命周期何吝,來添加相應的引用計數(shù)操作代碼溉委。所以,ARC
是工作在編譯期的一種技術方案爱榕。
這樣的好處是:編譯之后瓣喊,ARC
與非 MRC
代碼是沒有什么差別的,所以二者可以在源碼中共存黔酥。實際上藻三,你可以通過編譯參數(shù) -fno-objc-arc
來關閉部分源代碼的 ARC
特性。由于 ARC
能夠深度分析每一個對象的生命周期跪者,它能夠做到比 MRC
更加高效棵帽。
例如在一個函數(shù)中,對一個對象剛開始有一個引用計數(shù) +1
的操作渣玲,之后又緊接著有一個 -1
的操作逗概,那么編譯器就可以把這兩個操作都優(yōu)化掉。
2 ARC 的核心思想忘衍?
- 自己生成的對象逾苫,自己持有
- 非自己生成的對象,自己可以持有
- 自己持有的對象不再需要時枚钓,需要對其進行釋放
- 非自己持有的對象無法釋放
3 ARC 在使用時應該遵循的原則铅搓?
- 不能使用
retain
、release
搀捷、retainCount
星掰、autorelease
。 - 不可以使用
NSAllocateObject
指煎、NSDeallocateObject
蹋偏。 - 必須遵守內存管理方法的命名規(guī)則便斥。
- 不需要顯示的調用
Dealloc
至壤。 - 使用
@autoreleasePool
來代替NSAutoreleasePool
。 - 不可以使用區(qū)域
NSZone
枢纠。 - 對象性變量不可以作為
C
語言的結構體成員像街。 - 顯示轉換
id
和void*
黎棠。
4 ARC 在編譯時做了哪些工作?
- 自動調用
保留(retain)
與釋放(release)
的方法 - 相對于垃圾回收這類內存管理方案镰绎,ARC 不會帶來運行時的額外開銷脓斩,所以對于應用的運行效率不會有影響。
ARC
會把能夠互相抵消retain
畴栖、release
随静、autorelease
,操作簡化吗讶,如果發(fā)現(xiàn)在同一個對象上執(zhí)行了多次保留與釋放操作燎猛,那么ARC
有時可以成對的移除這兩個操作。
5 ARC 在運行時做了哪些工作照皆?
主要是指
weak
關鍵字重绷。weak
修飾的變量能夠在引用計數(shù)為0
時被自動設置成nil
,顯然是有運行時邏輯在工作的膜毁。關于原因會單獨開一個問題為了保證向后兼容性昭卓,
ARC
在運行時檢測到類函數(shù)中的autorelease
后緊跟其后retain
,此時不直接調用對象的autorelease
方法瘟滨,而是改為調用objc_autoreleaseReturnValue
候醒。
objc_autoreleaseReturnValue
會檢視當前方法返回之后即將要執(zhí)行的那段代碼,若那段代碼要在返回對象上執(zhí)行retain
操作杂瘸,則設置全局數(shù)據(jù)結構中的一個標志位火焰,而不執(zhí)行autorelease
操作,與之相似胧沫,如果方法返回了一個自動釋放的對象昌简,而調用方法的代碼要保留此對象,那么此時不直接執(zhí)行retain
绒怨,而是改為執(zhí)行objc_retainAoutoreleasedReturnValue
函數(shù)纯赎。此函數(shù)要檢測剛才提到的標志位,若已經(jīng)置位南蹂,則不執(zhí)行retain
操作犬金,設置并檢測標志位,要比調用autorelease
和retain
更快六剥。
_myPerson = [ECOPerson personWithName:@“Bob”];
ECOPerson * tmp = [ECOPerson personWithName:@“Bob”];
_myPerson = [tmp retain];
6 函數(shù)返回一個對象時晚顷,會對對象autorelease么?為什么疗疟?autorelease是什么時候釋放的该默?
會對對象 autorelease
,因為需要在稍后釋放對象策彤,從而給調用者留下足夠長的時間栓袖,使其可以在需要時先保留返回值匣摘。此方法可以保證對象在跨越方法調用邊界時一定存活。
除非你有自己的自動釋放池裹刮,否則這個時機就是當前線程,當前事件循環(huán)結束時音榜,就是 RunLoop
結束時(observer -> beforeWaiting)。
// 情況一:
@autoreleasepool {
NSObject * obj = [NSObject new];
[obj autorelease];
NSLog(@"%d",[obj retainCount]); //1
}
// 情況二:
NSObject * obj = [NSObject new];
[obj autorelease];
[obj autorelease];
NSLog(@"%d",[obj retainCount]);
NSLog(@"%d",[obj retainCount]);
// 崩潰
// 情況三:
NSObject * obj捧弃;
@autoreleasepool {
obj = [NSObject new];
[obj autorelease];
NSLog(@"%d",[obj retainCount]); // 1
}
//crash 出了大括號就已經(jīng)被銷毀了
NSLog(@"%d",[obj retainCount]);
7 為什么已經(jīng)有了 ARC 赠叼,還需要 @autoreleasePool?
提到 OC
的 RC
违霞,首先要橫向對比一下 Android
的 GC(垃圾回收機制)
梅割,GC
的內存回收是集中式回收(定期回收),而 RC
的回收是伴隨整個運行時的葛家,所以 android
機器有種 時“卡”時“流暢” 的感覺户辞,而 iOS
總體比較均勻,缺乏像 GC
的集中式回收內存的類似機制癞谒,所以猜測 Pool
的產(chǎn)生也是彌補 RC
的這一不足底燎,在 RC
基礎上進行內存優(yōu)化的一種手段。
8 簡要闡述內存相關的關鍵字弹砚?
Strong
Strong
修飾符表示指向并持有該對象双仍,其修飾對象的引用計數(shù)會加1。該對象只要引用計數(shù)不為0就不會被銷毀桌吃。當然可以通過將變量強制賦值nil
來進行銷毀朱沃。
Weak
weak
修飾符指向但是并不持有該對象,引用計數(shù)也不會加1茅诱。在Runtime
中對該屬性進行了相關操作逗物,無需處理,可以自動銷毀瑟俭。weak
用來修飾對象翎卓,多用于避免循環(huán)引用的地方。weak
不可以修飾基本數(shù)據(jù)類型摆寄。
assign
assign
主要用于修飾基本數(shù)據(jù)類型失暴,
例如NSInteger
,CGFloat
微饥,存儲在棧中逗扒,內存不用程序員管理。assign
是可以修飾對象的欠橘,但是會出現(xiàn)問題矩肩。
copy
copy
關鍵字和strong
類似,copy
多用于修飾有可變類型的不可變對象上NSString
,NSArray
,NSDictionary
上简软。
__unsafe_unretain
__unsafe_unretain
類似于weak
蛮拔,但是當對象被釋放后,指針已然保存著之前的地址痹升,被釋放后的地址變?yōu)?僵尸對象
建炫,訪問被釋放的地址就會出問題,所以說他是不安全的疼蛾。
__autoreleasing
將對象賦值給附有
__autoreleasing
修飾的變量等同于ARC
無效時調用對象的autorelease
方法,實質就是扔進了自動釋放池肛跌。
9 說一下什么是懸垂指針?什么是野指針察郁?
懸垂指針
指針指向的內存已經(jīng)被釋放了衍慎,但是指針還存在,這就是一個
懸垂指針
或者說迷途指針
野指針
沒有進行初始化的指針皮钠,其實都是
野指針
10 內存管理默認的關鍵字稳捆?
MRC
@property (atomic,readWrite,retain) NSString *name;
ARC
@property (atomic,readWrite,strong) NSString *name;
10 __weak 和 __unsafe_unretain 的區(qū)別精肃?
__weak
是__unsafe_unretain
升級版揭绑,__unsafe_unretain
在指向的內存地址銷毀后,指針本身并不會自動銷毀征椒,這也就造成了野指針款侵,之后容易造成 Crash末荐。__weak
在指向的內存銷毀后,可以將指針變量置為nil
新锈,這樣更加安全甲脏。
11 __weak 修飾的變量在地址被釋放后,為何被置為 nil妹笆?
在 Runtime
中專門維護了一個用于存儲 weak
指針變量的 weak
表块请,這實際上是一個 Hash
表。這個表 key
是 weak指針
所指向的內存地址拳缠,value
是指向這個內存地址的所有 weak指針
负乡,實際上是一個數(shù)組。
過程可以總結為3步
- 1脊凰、初始化時:
runtime
會調用objc_initWeak
函數(shù)抖棘,初始化一個新的weak指針
指向對象的地址。 - 2狸涌、添加引用時:
objc_initWeak
函數(shù)會調用objc_storeWeak()
函數(shù)切省,objc_storeWeak()
的作用是更新指針指向,創(chuàng)建對應的弱引用表帕胆。 - 3朝捆、釋放時,調用
clearDeallocating
函數(shù)懒豹。clearDeallocating
函數(shù)首先根據(jù)對象地址獲取所有weak指針
地址的數(shù)組芙盘,然后遍歷這個數(shù)組把其中的數(shù)據(jù)設為nil
驯用,最后把這個entry
從weak表
中刪除,最后清理對象的記錄儒老。
12 為什么在 MRC
已經(jīng)有 __weak
的情況下蝴乔,還需要 _unsafe_unretain
。驮樊?
兼容性考慮薇正。
iOS4
以及之前還沒有引入weak
,這種情況想表達弱引用的語義只能使用unsafe_unretained
囚衔。這種情況現(xiàn)在已經(jīng)很少見了挖腰。性能考慮。使用
weak
對性能有一些影響练湿,因此對性能要求高的地方可以考慮使用unsafe_unretained
替換weak
猴仑。
一個例子是YYModel
的實現(xiàn),為了追求更高的性能肥哎,其中大量使用unsafe_unretained
作為變量標識符宁脊。
13如何打破循環(huán)引用?
- 注意變量作用域贤姆,使用
autorelease
讓編譯器來處理引用榆苞。 - 使用弱引用(
__weak
)。 - 當實例變量完成工作后霞捡,將其置為
nil
坐漏。
14能不能用 assign
修飾 Nsobject
類型?
也可以碧信,但有可能出問題赊琳。
使用 assign
修飾 Nsobject
類型,賦值之后會被立即釋放砰碴,對應的屬性也就變成了野指針躏筏。
運行時跑到屬性有關操作會直接崩潰掉。
15 內存中的5大區(qū)都是什么呈枉?
- 棧區(qū)(stack):由編譯器自動分配釋放 趁尼,存放函數(shù)的參數(shù)值,局部變量的值等猖辫。其 操作方式類似于數(shù)據(jù)結構中的棧酥泞。
- 堆區(qū)(heap):一般由程序員分配釋放, 若程序員不釋放啃憎,程序結束時可能由OS回收 芝囤。注意它與數(shù)據(jù)結構中的堆是兩回事,分配方式倒是類似于鏈表。
- 全局區(qū)(靜態(tài)區(qū))(static):全局變量和靜態(tài)變量的存儲是放在一塊的悯姊,初始化的 全局變量和靜態(tài)變量在一塊區(qū)域羡藐, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。 - 程序結束后由系統(tǒng)釋放悯许。
- 文字常量區(qū):常量字符串就是放在這里的仆嗦。 程序結束后由系統(tǒng)釋放。
- 程序代碼區(qū):存放函數(shù)體的二進制代碼岸晦。
16 autoReleasePool
什么時候釋放?
App
啟動后欧啤,蘋果在主線程 RunLoop
里注冊了兩個 Observer
睛藻,其回調都是 _wrapRunLoopWithAutoreleasePoolHandler()
启上。
第一個 Observer
監(jiān)視的事件是 Entry(即將進入Loop)
,其回調內會調用 _objc_autoreleasePoolPush()
創(chuàng)建自動釋放池店印。其 order
是 -2147483647
冈在,優(yōu)先級最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調之前按摘。
第二個 Observer
監(jiān)視了兩個事件: BeforeWaiting
(準備進入休眠) 時調用_objc_autoreleasePoolPop()
和 _objc_autoreleasePoolPush()
釋放舊的池并創(chuàng)建新池包券;Exit
(即將退出Loop) 時調用 _objc_autoreleasePoolPop()
來釋放自動釋放池。這個 Observer
的 order
是 2147483647
炫贤,優(yōu)先級最低溅固,保證其釋放池子發(fā)生在其他所有回調之后。
在主線程執(zhí)行的代碼兰珍,通常是寫在諸如事件回調侍郭、Timer
回調內的。這些回調會被 RunLoop
創(chuàng)建好的 AutoreleasePool
環(huán)繞著掠河,所以不會出現(xiàn)內存泄漏亮元,開發(fā)者也不必顯示創(chuàng)建 Pool
了。
17 簡要說一下自動引用計數(shù)的數(shù)據(jù)存儲結構唠摹?
可以參考這篇文章 : 黑幕背后的Autorelease