這是我的第一篇文章,有什麼不對的地方還煩請大家指出啦,總覺得做開發很有必要做筆記,方便記錄自己的所得也能與大家探討,但是之前一直比較懶所以無所作為,那接下來的時間一起好好努力吧各位加油加油。
1.總起
1.1 瞭解記憶體管理
瞭解記憶體管理的最普通的方式是考慮所有權。如果Manny對Jack曾說過alloc,retain或者copy,則表示Manny已經宣稱對Jack的所有權。多個物件可以同時擁有Jack,但每個物件只負責正確地管理自己對Jack的所有權,最終釋放Jack是每個Jack所有者的責任,而Jack的非所有者永遠不需要釋放Jack。只要所有擁有Jack所有權的物件都以這種方式執行,Jack就不會洩漏也不會有任何指向Jack的指標留下來搖晃。
1.2 相關知識點
-
野指標:指標變數沒有進行初始化或指向的空間已經釋放。
- 使用野指標呼叫物件方法,會報異常,程式崩潰。
- 通常在呼叫完release方法後,把儲存物件指標的地址清空(不是銷燬物件,引用計數為0時才會呼叫dealloc銷燬物件),賦值為nil,但OC中沒有空指標異常,所以[nil retain]呼叫方法不會有異常。
-
記憶體洩漏:
-
在ARC自動引用計數模式下,造成記憶體洩漏的情況:
- 如Person *person = [Person new];(物件指標提前賦值nil或者清空,⚠️提前)在棧區中的person已經釋放,而堆區new產生的物件還沒有釋放,就會造成記憶體洩漏。
-
在MRC手動引用計數模式下,造成記憶體洩漏的情況:
- 沒有配對釋放,不符合記憶體管理原則。
- 物件指標提前賦值nil或者清空,導致release不起作用。
-
-
殭屍物件:堆中已經被釋放的物件(retainCount=0)。
-
空指標:指標賦值為空(nil)。
提醒
一個變數的名稱,包括例項變數,只是一個指標。當你向該指標傳送訊息時,你實際上就是通過該指標將訊息傳送到它指向的物件。記憶體管理的規則是關於物件的原則,而不是關於名稱、引用或指標的規則。你不能遞增或遞減一個指標的保留計數,因為沒有這個東西。指標所佔用的記憶體是自動管理的(而且很小)。記憶體管理所關注的是指標所指向的物件。
2.MRC
2.1 實現原理
Objective-C物件中儲存著引用計數這一整數值。呼叫alloc或者retain方法後,引用計數+1。呼叫release後,引用計數-1。引用計數為0時,呼叫dealloc方法廢棄物件。
2.2 相關操作
物件操作 | Objective-C方法 | 引用計數 |
---|---|---|
生成並持有物件 | alloc/new/copy/mutablecopy | 1 |
持有物件 | retain | +1 |
釋放物件 | release | -1 |
廢棄物件 | dealloc | 0 |
2.3 記憶體管理的思考方式
-
自己生成的物件,自己持有
NSObject *obj = [[NSObject alloc] init]; [obj retain]; NSLog(@"obj - %lu",[obj retainCount]); 複製程式碼
-
非自己生成的物件,自己也能持有
id obj = [NSMutableArray array]; [obj retain]; NSLog(@"obj - %lu",[obj retainCount]); 複製程式碼
-
不再需要自己持有的物件時釋放
NSObject *obj = [[NSObject alloc] init];//自己生成自己持有 [obj retain]; NSObject *obj2 = [obj retain];//非自己生成,自己持有 [obj release]; [obj2 release]; NSLog(@"obj - %lu",[obj retainCount]); 複製程式碼
-
非自己持有的物件自己無法釋放
id obj = [NSMutableArray array]; [obj release]; 複製程式碼
提醒
MRC下物件的引用計數可以通過[object retainCount]方法獲得。
2.4 NSAutoreleasePool
autorelease故名思議就是自動釋放,看上去很像ARC,但其實更類似於C語言中的區域性變數,也就是說,超出變數作用域的時候將自動被廢棄,但這裡與C語言不同的是,程式設計人員可以手動設定其作用域。 autorelease的具體使用方法如下:
- 生成並持有NSAutoreleasePool物件
- 呼叫已分配的autorelease方法
- 廢棄NSAutoreleasePool物件
NSAutoreleasePool物件的生命週期相當於C語言變數的作用域,對於所有呼叫過autorelease方法的物件,在廢棄NSAutoreleasePool物件時,都將對物件統一呼叫release方法,程式碼如下所示:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init] ;
id obj = [ [NSObject alloc]init];
[obj autorelease] ;
[pool drain];
複製程式碼
提醒
在Cocoa框架中,如果不是使用alloc/new/copy/mutablecopy這幾個方法返回的物件,其餘方法返回的物件都將自動註冊到NSAutoreleasePool中,id array = [NSMutableArray arrayWithCapacity:10];其實也就等同於:id array = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];
3.ARC
ARC是Objective-C編譯器的特性,而不是執行時特性或者垃圾回收機制,ARC所做的只不過是在程式碼編譯時為你自動在何時的位置插入release或者autorelease,減少了開發的工作量。但我們有時仍需要四種所有權修飾符來配合ARC來進行記憶體管理。
3.1 四種所有權修飾符
-
__strong:強引用,持有所指向物件的所有權,無修飾符情況下的預設值。如需強制釋放,可置nil(這裡先建立一個指標,將新的值retain一次,將指標動態指向新的值,並將舊的值release一次)。
NSObject *obj = [[NSObject alloc]init]; //他們是等價的 NSObject __strong *obj = [[NSObject alloc]init]; 複製程式碼
-
__weak:弱引用,不持有所指向物件的所有權,引用指向的物件記憶體被回收之後,引用本身會置nil,避免野指標.避免迴圈引用,會將物件註冊到autoreleasepool(既不保留新值,也不釋放舊值,動態地將指標指向新的值,如果這個值剛被dealloc,就會將指標更新為一個nil指標)。
-
unsafe_unretained:相當於assign。直接賦值。引用計數不變。他會發生野指標現象。所以不安全。不像weak,當指向的物件為空的時候,將指標置為nil。
-
_autoreleasing:將物件賦值給附有 _ autoreleasing 修飾符的變數等同於ARC 無效時呼叫物件的autorelease方法。
@autoreleasepool { id __autoreleasing obj = [[NSObject alloc] init]; } 複製程式碼
3.2 底層實現
-
__strong底層實現
-
alloc/new/copy/mutablecopy情況下
//alloc為例的模擬底層程式碼為: id __Strong obj = [[NSObject alloc] init]; id obj = objc_msgSend(NSobject, @selector(alloc)); objc_msgSend(obj, @selector(init)); objc_release(obj); //編譯器自動幫我們加入了release,來釋放物件 複製程式碼
-
非alloc/new/copy/mutablecopy情況下
//array為例的模擬底層程式碼為: id obj = objc_msgSend(NSMutableArray, @selector(array)); objc_retainAutoreleasedReturnValue(obj); objc_release(obj); //objc_retainAutoreleasedReturnValue(obj)是主要用於最優化程式執行 //array方法的底層模擬為: id obj = objc_msgSend(NSmutableArray,@selector(alloc)); objc_msgSend(obj,@selector(init)); return objc_autorealeaseReturnValue(obj); 複製程式碼
objc_autorealeaseReturnValue(obj)與objc_retainAutoreleasedReturnValue(obj)是成對的,objc_autorealeaseReturnValue(obj)會把物件註冊到autorealeasepool中,但是如果它檢測到方法執行列表中出現objc_retainAutoreleasedReturnValue(obj)方法,那麼就不會將返回的物件註冊到autorealeasepool,而是直接傳遞到方法和函式的呼叫方。這樣直接傳遞可以達到最優化。
-
-
__weak底層實現
-
__weak原始碼
id __weak obj1 = obj; //模擬程式碼 id obj1; obj1 = 0; objc_storeWeak(&obj1,obj); objc_destoryWeak(&obj1); 等同於objc_storeWeak(&obj1,0); 複製程式碼
objc_storeWeak(&obj1,obj)函式將第二個引數的賦值物件的地址作為鍵值,將第一個引數的附有__weak修飾符的變數的地址註冊到weak表中,如果第二個引數為0,則把變數的地址從weak中刪除。一個鍵值可以註冊多個變數的地址由此可見,如果大量的weak變數,則會消耗CPU資源,所以weak 只用來避免迴圈引用。
-
__weak與@autoreleasepool
id __weak obj1 = obj; NSLog(@"%@",obj1); //模擬程式碼 id obj1; objc_initWeak(&obj1,obj); id temp = objc_loadWeakRetained(&obj1); objc_autorelease(temp); NSLog(@"%@",temp); objc_destoryWeak(&obj1); 複製程式碼
所以在@autorealeasepool塊結束前可以放心使用weak修飾變數
-
__autoreleasing底層實現
將物件賦值給附有__autoreleasing修飾符的變數等同於ARC無效時,呼叫物件的autorelease方法。
-
使用alloc/new/copy/mutableCopy時
@autoreleasepool { id __autoreleasing obj = [[NSObject alloc] init]; } //模擬程式碼 id pool = objc_autoreleasePoolPush(); id obj = objc_msgSend(NSObject, @selector(alloc)); objc_msgSend(obj, @selector(init)); objc_autorelease(obj); objc_autoreleasPoolPop(pool); 複製程式碼
-
使用alloc/new/copy/mutableCopy以外的方法時
@autoreleasepool { id __autoreleasing obj = [NSMutableArray array]; } // 模擬程式碼 id pool = objc_autoreleasePoolPush(); id obj = objc_msgSend(NSMutableArray,@selector(array)); objc_retainAutoreleasedReturnValue(obj); objc_autorelease(obj); objc_autoreleasPoolPop(pool); // 雖然 obj 持有物件的方法變為 objc_retainAutoreleasedReturnValue, 但是將 obj 所引用的物件註冊到 autoreleasepool 中的方法並沒有改變 複製程式碼
關於@autoreleasepool,在ARC下應該使用@autoreleasepool而不是NSAutoreleasePool,@autoreleasepool的具體實現將在之後的文章中繼續探討。
-
-
3.3 使用規則
- 不能使用retain/release/retainCount/autorelease
- 不過載dealloc(如果是釋放物件記憶體以外的處理,是可以過載該函式的,但是不能呼叫[super dealloc])
- 不能使用NSAllocateObject, NSDeallocateObject
- 不能在C結構體中使用物件指標
- id與void *間的如果cast時需要用特定的方法(__bridge關鍵字)
- 不能使用NSAutoReleasePool,而需要使用@autoreleasepool塊
- 不能使用區域(NSZone)
4.屬性的記憶體管理
ObjC2.0引入了@property,提供成員變數訪問方法、許可權、環境、記憶體管理型別的宣告,下面主要說明ARC中屬性的記憶體管理。屬性的引數分為三類,基本資料型別預設為(atomic,readwrite,assign),物件型別預設為(atomic,readwrite,strong),其中第三個引數就是該屬性的記憶體管理方式修飾,修飾詞可以是以下之一:
-
assign
直接賦值,一般用來修飾基本資料型別。當然也可以修飾ObjC物件,但是不推薦,因為被assign修飾的物件釋放後,指標還是指向釋放前的記憶體,在後續操作中可能會導致記憶體問題引發崩潰。
@property (nonatomic, assign) NSInteger count; 複製程式碼
-
retain
retain和strong一樣,都用來修飾ObjC物件,使用set方法賦值時,實質上是會先保留新值,再釋放舊值,再設定新值,避免新舊值一樣時導致物件被釋放的的問題。
//MRC寫法如下 - (void)setCount:(NSObject *)count { [count retain]; [_count release]; _count = count; } //ARC對應寫法 - (void)setCount:(NSObject *)count { _count = count; } 複製程式碼
-
copy
一般用來修飾String、Dict、Array等需要保護其封裝性的物件,尤其是在其內容可變的情況下,因此會拷貝(深拷貝)一份內容給屬性使用,避免可能造成的對源內容進行改動。使用set方法賦值時,實質上是會先拷貝新值,再釋放舊值,再設定新值。實際上,遵守NSCopying的物件都可以使用copy,當然,如果你確定是要共用同一份可變內容,你也可以使用strong或retain。
-
weak
ARC新引入修飾詞,可代替assign,比assign多增加一個特性(置nil)。weak和strong一樣用來修飾ObjC物件。使用set方法賦值時,實質上不保留新值,也不釋放舊值,只設定新值。
@property (weak) id<MyDelegate> delegate; 複製程式碼
-
strong
ARC新引入修飾詞,可代替retain,ARC一般都寫strong。
Person *per = [[Person alloc] init];
self.person = per;
如果是strong,物件的retainCount為2,如果為weak,物件的retainCount為1。
-
unsafe_unretained
等價於assign,可以用來修飾資料型別和OC物件,但是不會使計數器加1,且物件銷燬時也不會將物件指向nil,容易造成野指標錯誤。
5.block的記憶體管理
OC中使用block必須自己管理記憶體,錯誤的記憶體管理將導致迴圈引用等記憶體洩漏問題,這裡主要說明在ARC下block宣告和使用的時候需要注意的兩點:
-
如果你使用@property去宣告一個block的時候,一般使用copy來進行修飾(當然也可以不寫,編譯器自動進行copy操作),儘量不要使用retain。
@property (nonatomic, copy) void(^block)(NSData * data); 複製程式碼
-
block會對內部使用的物件進行強引用,因此在使用的時候應該確定不會引起迴圈引用,當然保險的做法就是新增弱引用標記。
__weak typeof(self) weakSelf = self; 複製程式碼