細節(jié)決定成敗,iOS移動開發(fā)發(fā)展已經(jīng)多年,相信內(nèi)存管理這個概念已經(jīng)被說爛了,但是相信對于很多小伙伴來說,只是了解了內(nèi)存管理的大概知識體系,對于一些邊緣知識點掌握的并不是很好,邊緣不代表不重要.
互聯(lián)網(wǎng)環(huán)境寒冬將至,為了應(yīng)對目前市場越來越高的需求,iOS開發(fā)不得不去提升自己的能力去匹配更高的需求,當每個人都學會了的時候,你比別人更好的體現(xiàn)就是你會的內(nèi)容更深入,進入正題,下面是總結(jié)的一些內(nèi)存管理方面的使用注意事項.
-
內(nèi)存管理方法名命名規(guī)則
-
alloc/new/copy/mutable
以上名稱開頭的方法名,返回自己生成并持有的對象,如果沒有引用的變量則會釋放并廢棄.// 示例 // main.m實現(xiàn)如下 #import <Foundation/Foundation.h> #import "CFObject.h" int main(int argc, const char * argv[]) { @autoreleasepool { id __weak obj; @autoreleasepool { obj = [CFObject newObject]; NSLog(@"%@", obj); } NSLog(@"%@", obj); } return 0; } //CFObject.h中做函數(shù)聲明 // CFObject.m實現(xiàn)如下 + (id)allocObject { return [[CFObject alloc] init]; } + (id)newObject { return [[CFObject alloc] init]; } // 打印結(jié)果為null null /** 結(jié)果分析: 由于obj指針為__weak弱指針,所以并不持有生成的對象,編譯器判斷沒有持有當前對象的變量,所以在newObject方法執(zhí)行結(jié)束后將對象釋放并廢棄,obj此時指向nil,第一個log打印null 第二個log打印時obj也不再指向?qū)ο?所以obj指針指向nil,打印結(jié)果為null */
你可能會說,這不就是內(nèi)存管理最基本的原則嗎,不用了就釋放.這進階什么了,剛學的時候就已經(jīng)理解了.別急,繼續(xù)看下面的例子
// 在CFObject.m中增加一個createObject方法,返回實例對象 + (id)createObject { return [[CFObject alloc] init]; } // main.m中和上一個示例一樣調(diào)用并打印 int main(int argc, const char * argv[]) { @autoreleasepool { id __weak obj; @autoreleasepool { obj = [CFObject createObject]; NSLog(@"%@", obj); } NSLog(@"%@", obj); } return 0; } /** 猜一下打印結(jié)果是什么,還是null, null 嗎? 結(jié)果是 : <CFObject: 0x102805ef0> 和 null 為什么返回實例的實現(xiàn)都一樣,更換了方法名就打印不一樣了呢,這就引出了第二條命名規(guī)則 */
-
非第一條提到的名稱命名的方法名
除了第一條以外命名的方法名,例如createObject NSMutablArray的 array方法 [NSMutablArray array],這一類的方法返回實例時并不持有對象,但是為了能達到使用對象的目的,使用了autorelease自動釋放池管理對象內(nèi)存.
```
// 所以上面示例中的createObject方法實現(xiàn)可以轉(zhuǎn)換為返回autorelease 對象.類似以下實現(xiàn)(僅邏輯類比,并非真正實現(xiàn))
+ (id)createObject {
return [[[CFObject alloc] init] autorelease];
}/** 結(jié)果分析 : 由于createObject返回實例對象被自動釋放池管理,所以不會立馬釋放對象,當一個log打印時,當前對象存在,可以有打印結(jié)果, 當autoreleasepool{}結(jié)束作用域時,對象被釋放,所以第二個打印為null. */ ```
ARC下第三條規(guī)則(引用自O(shè)bjective-C高級編程一書中Page 52 內(nèi)容)
init:init方法規(guī)則比alloc/new/copy/mutableCopy 還要嚴格,該方法:
1. 必須返回的是實例方法
2. 返回對象類型為id或者聲明類的對象類型,或者是該類的父類或子類
3. 返回對象不注冊到autoreleaspool上,只是對alloc返回的對象初始化并返回該對象,init方法源碼中調(diào)用runtime的_objc_rootInit直接將返回的對象返回,未作處理.(參考runtime源碼NSObject.mm的init方法實現(xiàn))
-
-
內(nèi)存管理修飾符詳解
- 修飾符
- __strong : 強引用指針,用于ARC下對變量修飾并產(chǎn)生強引用持有賦值對象,默認環(huán)境下的修飾符,一般 id object 書寫格式默認就是id __strong object.使用非常常見.
- __weak:弱引用指針,用于ARC下對變量修飾產(chǎn)生弱引用,不持有對象,常用于解決循環(huán)引用問題.當指向?qū)ο蟛淮嬖跁r被置為nil.
- 不支持__weak的情景:
- 大多數(shù)重寫了retain/release等內(nèi)存管理方法的類,由于其自己實現(xiàn)了引用機制,而__weak需要用到一些runtime運行時庫的函數(shù),所以此種情況的類不支持__weak修飾,一般聲明中會有提示,并且編譯器編譯時報錯
- 實現(xiàn)了allowsWeakReference或者retainWeakReference方法并且返回NO的情況
- 當實現(xiàn)allowsWeakReference方法返回No時表示不支持弱引用,在賦值給__weak修飾的變量時,程序會異常終止
- 當實現(xiàn)retainWeakReference方法返回NO時,__weak修飾的指針變量會被置為nil.
// 還是用以上示例中的CFObject類實現(xiàn)方法如下 - (BOOL)retainWeakReference { return YES; } // main.m做以下測試 int main(int argc, const char * argv[]) { @autoreleasepool { id object = [[CFObject alloc] init]; id __weak obj = object; NSLog(@"%@", obj); } return 0; } /** 當return NO時,打印null; 當return YES時,打印對象 */
- __weak修飾的變量使用時會調(diào)用objc_loadWeakRetained()/objc_release()方法對做retain/release操作.大量使用__weak會產(chǎn)生性能損耗(大量做retain/release操作),
- 不支持__weak的情景:
- __autorelease
- C中動態(tài)數(shù)組不支持autorelease(參考OC高編 P65)
- autorelease修飾auto自動變量限制(僅限于局部變量,函數(shù)以及方法參數(shù))
- 非顯示使用__autorelease修飾也生效的兩種情況:
- 第一種就是命名規(guī)則提到的第二條,雖然沒顯示的__autorelease修飾,但是編譯器會做添加邏輯處理
- 類似id obj 是默認 __strong修飾一樣,如果是對象的指針id *obj 或者NSObject ** object 這樣,默認是__autorelease.示例就是當傳入?yún)?shù)為對象指針類型,例如NSError的使用情景下,(該情況本人未通過代碼驗證,參考文章是OC 高級編程一書中的介紹)
NSError *error; // 聲明 method:(NSError **)error; // 調(diào)用 method:&error // 此時method:(NSError **)error;聲明時,可以看做是method:(NSError * __autorelease *)error;
- autorelease 和__strong 共同使用時優(yōu)化方案;
當遇到命名規(guī)則中的第二條情況時,方法返回的對象需要做autorelease處理,如果對象賦值給__strong修飾的變量則又會做一個retain處理,示例如下:+ (id)createObject { return [[CFObject alloc] init]; } // main.m id obj = [CFObject createObject]; clang編譯成c++代碼之后, // CFObject.m + (id)createObject { id tem1 = ((id(*)(id, SEL))objc_msgSend)([NSObject class], @selector(alloc)); id tem2 = ((id(*)(id, SEL))objc_msgSend)(tem1, @selector(init)); return objc_autoreleaseReturnValue(id2); } // main.m id obj_tmp = ((id(*)(id, SEL))objc_msgSend)(self, @selector(obj)); id obj = objc_retainAutoreleasedReturnValue(obj_tmp); /** main.m中,編譯器會在返回為autorelease對象賦值后自動插入objc_retainAutoreleasedReturnValue()方法, 此時先autorelease,然后再retain放到自動釋放池對象的操作會顯得很沒效率. apple其實是對這個做了優(yōu)化處理的,處理邏輯如下(參考OC 高級編程 P67): 1. 在類似命名規(guī)則第二條情況時,返回autorelease對象是通過函數(shù)objc_autoreleaseReturnValue()函數(shù)實現(xiàn)的. 使用到objc_autoreleaseReturnValue()函數(shù)時,編譯器并不會決定立馬將對象放入自動釋放池,而是去查看調(diào)用方的執(zhí)行命令列表,是否會有objc_retainAutoreleasedReturnValue()操作,如果沒有,則按照正常的流程將對象放入自動釋放池,如果有調(diào)用,則不再方式釋放池,而是通過修改某個全局數(shù)據(jù)結(jié)構(gòu)標志位的方式,當調(diào)動到objc_retainAutoreleasedReturnValue()方法時,會去檢測該標志位,如果已經(jīng)設(shè)置修改過,則不會再做retain操作, 通過修改標志位的方式效率比retain/release 會更高 */
- unsafe_unretain : 同__weak一樣,都是弱引用,差異區(qū)別會放在下一知識點說明.
- __weak 和unsafe_unretain 修飾符的差異
- 空指針骇塘、野指針占贫、懸垂指針的差異
空指針很好理解,就是指向NUll的指針,但是針對野指針(Wild pointer)和懸垂指針(Dangling pointer)則有不同的理解.
在某些語言中,是不區(qū)分野指針和懸垂指針的,因為會Wild pointer 會被導向至 Dangling pointer ;對懸垂指針的理解是指向了已經(jīng)被釋放的內(nèi)存地址,但是指針未被置空.對野指針的理解是未初始化的指針或者是前面懸垂指針的情況,所以一般OC里也會說指向被釋放內(nèi)存地址的指針未野指針.對二者沒有明顯的區(qū)分. - 二者差異以及使用場景
__weak 和unsafe_unretained都是弱引用指針,區(qū)別在于__weak是ARC下的弱引用,對不存在ARC機制以前使用unsafe_unretained修飾,從名字也可以看出后者是不安全的,即當對象被釋放廢棄后,指針不會被置為nil,還指向原來的內(nèi)存空間,此時使用有可能會導致程序異常.
- 空指針骇塘、野指針占贫、懸垂指針的差異
- 修飾符
-
如何獲取引用計數(shù)值
- 因為在ARC環(huán)境下不允許使用ratainCount(MRC支持),所以如何獲取引用計數(shù)來調(diào)試內(nèi)存管理呢,
- OC對象獲取方式:_objc_rootRetainCount()
- C對象獲取方式:CFGetRetainCout()
獲取引用計數(shù)是蘋果思想里面不推薦的,因為引用計數(shù)并不是非常明顯的代表著內(nèi)存管理的結(jié)果.例如autorelease下的引用計數(shù),會默認在autoreleasepool pop時 做release操作,但是當你打印時還未執(zhí)行pop操作就會導致和你認為的數(shù)值不符,我們應(yīng)該換一種角度看待引用計數(shù)的思想,他并不是真的想讓開發(fā)者根據(jù)數(shù)值去控制內(nèi)存管理,真正的思想是根據(jù)是否存在強引用的思想去理解內(nèi)存管理.通過成對的retain/release去控制引用.所以此處的引用計數(shù)只是作為debug的一個參考,不可太過依賴.
另外知其然,知其所以然.對于內(nèi)存管理相關(guān)的源碼分析會放在以后幾章節(jié)詳細說明.
還在學習的路上奔波,所以有的知識理解還存在很大誤區(qū),非常歡迎指出共同進步...