內存管理
對於這篇呢,其實現在都是ARC模式,正常狀態下基本不用我們去手動釋放內存,所以如果不是要面試呀、裝逼或者扎實功底的,就先別看了或者了解下即可,因為像面試時,有些面試官想看你的基礎時,就有些人會問,現在工作基本不會用到。
學習目標
1. 掌握內存管理的原理
2. 掌握手動內存管理
===============================================
1.需要理解的知識
1.1內存管理
1.1.1 C的內存管理,以及麻煩之處
char *p = (char *)malloc(100*sizeof (char));
這是C的動態內存分配,我們手動跟系統申請了100個字節的內存;或者說系統在堆裡開辟了100個字節的空間,並將這個空間的首地址返回給指針變量p。
strcpy(p,"Hello World!");
將字符串拷貝給指針變量p指向的內存空間。
puts(p);
將p指針指向的內存空間裡的字符串打印出來。
free(p);
使用完成後,手動跟系統釋放內存空間;或者說系統回收空間。
如上就是C裡簡單的內存管理。
C的內存管理,我們手動申請,手動釋放。這樣來看,我們只需要注意兩個問題就好了:
1,申請內存,使用完成後需要釋放,如果不釋放會造成內存洩露。
2,不能多次釋放,如果多次釋放,則會崩潰。
但是,如果項目比較復雜,需要有幾十上百號人一起分工完成,就很容易出現問題。
比方說我們開辟了一塊內存空間,裡存放了一塊很有用的數據。但是,這個數據不只有我在這一塊代碼裡用,甚至有多個人,在程序的多個地方使用。這樣造成的結果就是,就算我使用完成這塊內存,我也不能去釋放他,因為我不能確定,別人在別的地方是否還需要使用這塊內存。內存洩露在所難免了。
OC的內存管理:
1.1.2 引用計數(retainCount)
對於一塊動態申請的內存,有一個人(指針)使用,就給這個內存的計數器加1,使用完成後,就給這個計數器減1,當這個內存的引用計數為0了,我們再釋放他,這樣,上面的問題就解決了。OC,就是使用引用計數這種方式來管理內存的。
1.1.3 內存管理的黃金法則
對於引用計數來說,有一套內存管理的黃金法則:
The basic rule to apply is everything that increases the reference counter with alloc, [mutable]copy[withZone:] or retain is in charge of the corresponding [auto]release.
如果對一個對象使用了alloc、copy、mutablecopy、retain,new,那麼你必須使用
相應的release或者autorelease。
通俗一點的說法就是誰污染誰治理。
1.1.4 objective-C的內存管理遵守下面這個簡單的策略:
1.你擁有你創建的對象,也就是說創建的對象(使用alloc,new,copy或者mutalbeCopy等方法)的初始引用計數是1。
2.給對象發送retain消息後,你擁有了這個對象 ,retainCount+1
3.當你不需要使用該對象時,發送release或者autorelease消息放棄這個對象
4.不要對你不擁有的對象發送“放棄”的消息
1.1.4 MRC和ARC
ARC Automatic Reference Counting,自動引用計數,由xcode,幫我們去管理內存。
MRC Manual Reference Counting,手動引用計數,我們手動管理內存。
Xcode 5.0 版本以後默認是ARC模式,
1.1.5 如何將工程改為MRC
xcode5,工程創建的時候是ARC的,我們如果想要MRC,需要進行如下設置。
選中工程 - target - Bulid Settings -Automatic Reference Counting改為NO。
1.1.6 ARC執行了新的規則
●開發者不能顯示調用dealloc;不能實現和調用retain、release、retainCount和autorelease。
禁止使用@selector(retain),@selector(release)等等。
開發者仍可以實現dealloc方法,如果你想管理資源而不是變量。
ARC中自定義的dealloc方法,不需要調用[super dealloc](其實這樣做就會導致編譯錯誤),編譯器會強制自動鏈接到父類。
開發者仍可以對Core Foundation-style對象,使用CFRetain,CFRelease和其他相關方法。
● 開發者不能使用NSAutoreleasePool對象。ARC下使用@autoreleasepool,它比NSAtuoreleasePool更有效率。
為了配合手動引用計數,ARC的方法命名有限制:
● 訪問器方法不能已new開頭,反過來就是:開發者不能聲明一個已new開頭的屬性,除非你給你指定一個getter
// 不正確 @property NSString *newTitle; // 正確 @property (getter=theNewTitle) NSString *newTitle;
1.1.7.野指針錯誤形式在Xcode中通常表現為:Thread 1:EXC_BAD_ACCESS(code=EXC_I386_GPFLT)錯誤。因為你訪問了一塊已經不屬於你的內存。
2.需要記住的知識
2.1 alloc與release
創建一個Dog類
@interface Dog : NSObject @end @implementation Dog - (void)dealloc { NSLog(@"dog dealloc"); [super dealloc]; } @end
delloc裡的析構函數,當對象銷毀的時候,會自動調用這個方法,我們在這裡重寫這個方法。
在main函數裡,寫入如下代碼:
int main(int argc, const char * argv[]) { @autoreleasepool { Dog *dog = [[Dog alloc] init]; } NSLog(@"程序即將退出"); return 0; }
從終端打印信息來看,程序即將退出這條打印之前,已經打印dog dealloc,也就是說在程序運行結束前,dog對象已經銷毀了。這個是ARC,由xcode幫我們管理dog對象。
將ARC改為MRC,再執行程序,dog對象並沒有銷毀,因為我們現在是手動管理了,我們需要遵守內存管理的黃金法則,Dog *dog = [[Dog alloc] init]; 我們需要對dog進行release。將main函數代碼改為如下形式:
int main(int argc, const char * argv[]) { @autoreleasepool { Dog *dog = [[Dog alloc] init]; [dog release]; } NSLog(@"程序即將退出"); return 0; }
再次執行程序,從打印可以看出,dog對象,已經銷毀。這就是黃金法則,我們對dog進行alloc,就要對dog進行release。
注意,release 並不是銷毀對象,讓對象的引用計數減1,當對象的引用計數為0的時候,自動調用dealloc方法,銷毀對象。
2.2 retain與retainCount
retain,將對象進行保留操作,也就是使對象的引用計數加1。
retainCount,打印一個對象的引用計數。
2.3 類的復合中使用
在上面代碼中,增加Person類
@interface Person : NSObject { // 一個人,養了一條狗(持有一條狗) Dog *_dog; } - (void)setDog:(Dog *)dog; - (Dog *)dog; @end @implementation Person /* 版本1 (有問題) 人並沒有真正持有狗,如果在main函數裡[dog release],讓dog的引用計數減1,就變為0,dog就銷毀了。 - (void)setDog:(Dog *)dog { _dog = dog; } */ /* 版本2 (有問題) 如果人再持有別的狗,就會造成第一條狗得不到釋放,內存洩露。 - (void)setDog:(Dog *)dog { _dog = [dog retain]; } */ /* 版本3 (有問題) 如果本來持有一條狗,又重新設置這條狗,先進行release,這個時候,很可能dog就銷毀了,然後,就沒法再次retain了。 - (void)setDog:(Dog *)dog { [_dog release]; _dog = [dog retain]; } */ // 版本4 OK!,標准寫法 - (void)setDog:(Dog *)dog { if (_dog != dog) { [_dog release]; _dog = [dog retain]; } } - (Dog *)dog { return _dog; } - (void)dealloc { NSLog(@"person dealloc"); // 人在銷毀的時候,一並將持有的dog對象銷毀 [_dog release]; [super dealloc]; }
//MRC:
黃金法則:
只要使用了alloc/retain/copy/mutableCopy,new, 創建了對象
那麼就必須使用release進行釋放,
———總結一句話就是:誰創建,誰負責釋放
retain — 使對象的引用計數+1, 如果指針需要去持有這個對象
需要使用retain
retainCount: 返回對象的引用計數值
release : — 使對象的引用計數 -1, 而不是釋放對象
dealloc:對象銷毀的時候(也就是retainCount為0的時候)自動調用這個方法
MRC:
2.4 @property retain,assign,copy展開
2.4.1 retain展開
如上代碼裡,Person的setter和getter方法,也可以用property,寫成如下形式
@property (nonatomic, retain) Dog *dog;
則會展開如下:
- (void)setDog:(Dog *)dog { if (_dog != dog) { [_dog release]; _dog = [dog retain]; } }
2.4.2 assign展開
//簡單數據類型 ,OC的內存管理對於簡單的數據類型 int\float…,
@property (nonatomic, assign) Dog *dog;,assign是直接賦值,則會展開如下: - (void)setDog:(QFDog *)dog { _dog = dog; }
2.4.3 copy展開 , 復制一份原來的對象
//copy 多用於字符串
@property (nonatomic, copy)NSString *name; 展開如下: - (void)setName:(NSString *)name { if (_name != name) { [_name release]; _name = [name copy]; } }
2.4 字符串內存管理
2.4.1 字符串的內存管理
// 對於字符串而言,非常不遵守黃金法則! (如果從字符串的引用計數來看,亂七八糟!) 這只是一個表象! 其實內部還是遵循的!!
// 我們要做的是,我們依舊遵守我們的黃金法則!
因此,如果是NSString,我們的property格式寫成如下: @property (nonatomic, copy) NSString *name;
2.4.2 copy和mutableCopy
2.5 數組的內存管理
結論
1)當我們創建數組的時候,數組會對每個對象進行引用計數加1
2)當數組銷毀的時候,數組會對每個對象進行引用計數減1
3)當我們給數組添加對象的時候,會對對象進行引用計數加1
4)當我們給數組刪除對象的時候,會對對象進行引用計數減1
總之,誰污染誰治理,管好自己就可以了(數組內部也遵守內存管理)。
2.6 autorelease與autoreleasepool
在main函數裡寫如下代碼:
int main(int argc, const char * argv[]) { @autoreleasepool { Dog *dog = [[Dog alloc] init]; //dog並沒有馬上銷毀,而是延遲銷毀,將dog對象的擁有權交給了autoreleasepool [dog autorelease]; //這個是可以打印的,因為打印完dog的引用計數後,dog對象才銷毀 NSLog(@"retainCount = %lu",dog.retainCount); } NSLog(@"程序即將退出"); return 0; }
autoreleasepool相當於一個數組,如果哪個對象發送autorelease消息,實際將對象的擁有權交給了autoreleasepool;當autoreleasepool銷毀的時候,autoreleasepool裡持有的對象都發送一個release消息。
2.7 加方法的內存管理
我們用加方法創建的對象,不用我們release,是因為類內部的實現使用了autorelease,延遲釋放
在Dog類的聲明裡增加一個加方法
+ (id)dog;
在Dog類的實現裡進行實現
+ (id)dog
{
注意,這裡不要寫成release,如果是release,那麼剛創建就銷毀了,使用autorelease,使得將對象的擁有權交給了自動釋放池,只要自動釋放池沒有銷毀,dog對象也就不會銷毀。
return [[[Dog alloc] init] autorelease];
}
2.8 對於自動內存釋放簡單總結一下:
1 autorelease方法不會改變對象的引用計數器,只是將這個對象放到自動釋放池中; 自動釋放池實質是當自動釋放池銷毀後調用對象的release方法,不一定就能銷毀對象(例如如果一個 對象的引用計數器>1則此時就無法銷毀); 由於自動釋放池最後統一銷毀對象,因此如果一個操作比較占用內存(對象比較多或者對象占用資源比較多),最好不要放到自動釋放池或者考慮放到多個自動釋放池; ObjC中類庫中的靜態方法一般都不需要手動釋放,內部已經調用了autorelease方法;
=====================================
ARC模式下的關鍵字:
__strong/__weak/__unsafe_unretain
開發者需要正確修飾變量。使用下面的格式來修飾變量聲明。
類名* 修飾 變量名
例如:
MyClass * __weak myWeakReference; MyClass * __unsafe_unretained myUnsafeReference;
對應的@property 參數分別為
strong/weak/unsafe_unretain
__strong : 強引用,相當於MRC下的retain,指針對對象具有決定的占
有,默認情況。
__weak : 弱引用,指針對對象不具有決定的占有,相當於MRC下的
assign,對象釋放後,指針賦值為nil。
__unsafe_unretain:弱引用,指針對對象不具有決定的占有,相當於MRC下的assign,對象釋放後,指針為懸垂指針(不會賦值為nil),可以會出現野指針,不建議使用。
@property(nonatomic, strong) xxx
//set 類似於 retain 展開 [name retain]
@property(nonatomic, weak) xxx
//類似於 assign
@property(nonatomic, unsafe_unretain) xxx
//類似於 assign