OC語言--內存管理
1.內存管理原理的介紹
1.1C的內存管理
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。不能多次釋放。假設多次釋放,則會崩潰。
可是,假設項目比較復雜,須要有幾十上百號人一起分工完畢,就非常easy出現故障。
例如說我們開辟了一塊內存空間。裏面存放了一塊非常實用的數據。可是,這個數據不僅僅有我在這一塊代碼裏用,甚至有多個人,在程序的多個地方使用。這樣造成的結果就是,就算我使用完畢這塊內存,我也不能去釋放他,由於我不能確定,別人在別的地方是否還須要使用這塊內存。內存泄露在所難免了。
2.OC的內存管理方式:引用計數
2.1引用計數
對於一塊動態申請的內存,有一個人(指針)使用。
OC。就是使用引用計數這樣的方式來管理內存的。
2.2內存管理的黃金法則
對於引用計數來說。有一套內存管理的黃金法則:
The basic rule to apply is everything that increases the reference counterwith alloc, [mutable]copy[withZone:] or retain is in charge of the corresponding [auto]release.
假設對一個對象使用了alloc、[mutable]copy、retain,那麽你必須使用對應的release或者autorelease。
通俗一點的說法就是誰汙染誰治理。
2.3MRC和ARC
ARC Automatic Reference Counting,自己主動引用計數,由xcode。幫我們去管理內存。
MRC Manual Reference Counting,手動引用計數,由我們手動管理內存。
但就眼下來看,非常多公司還是使用MRC.
2.4 怎樣將project改為MRC
xcode5。project創建的時候是ARC的。我們假設想要MRC,須要進行例如以下設置。
選中project - >target - >Bulid Settings - >搜索:automatic reference counting或auto。將Objective-C Automatic Reference Counting改為NO。
3.手動內存管理的操作(MRC)
3.1alloc與release
創建一個新的project,先別將內存管理改為手動
創建一個Dog類
@interface Dog : NSObject @end @implementation Dog - (void)dealloc { NSLog(@"dog dealloc"); [super dealloc]; } @end
dealloc裏的析構函數,當對象銷毀的時候。會自己主動調用這種方法。我們在這裏重寫這種方法。
在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方法並銷毀對象。
3.2 retain與retainCount
retain,將對象進項保留操作。也就是使對象的引用計數加1。
retainCount,打印一個對象的引用計數。
將main函數代碼改為例如以下形式:
int main(int argc, const char * argv[]) { @autoreleasepool { Dog *dog = [[Dog alloc] init]; //此時打印的結果,retainCount值為1, //也就是我們alloc。創建dog對象時,對象的引用計數為1 NSLog(@"dog retainCount = %lu",[dog retainCount]); //dog1指針要使用(引用)dog對象, //此時,為避免dog對象進行release。 //使得引用計數減1變為0,銷毀對象。 //我們進行了retain操作。 Dog *dog1 = [dog retain]; //此時打印的結果,retainCount值為2 NSLog(@"dog retainCount = %lu",[dog retainCount]); Dog *dog2 = [dog retain]; //此時打印的結果,dog,dog1,dog2,retainCount值都為3, //由於這三個指針指向同一個對象。 NSLog(@"dog retainCount = %lu",[dog retainCount]); NSLog(@"dog1 retainCount = %lu",[dog1 retainCount]); NSLog(@"dog2 retainCount = %lu",[dog2 retainCount]); //release 並非銷毀對象,讓對象的引用計數減1 [dog release]; //此時打印的結果,dog,dog1,dog2,retainCount值都為2, //盡管dog運行了release,但dog指針還是指向那個對象。//此時dog對對象僅僅有使用權,而沒有擁有權。 NSLog(@"dog retainCount = %lu",[dog retainCount]); NSLog(@"dog1 retainCount = %lu",[dog1 retainCount]); NSLog(@"dog2 retainCount = %lu",[dog2 retainCount]); [dog1 release]; [dog2 release]; //運行完上面兩句話的時候,dog對象就銷毀了。 //盡管這裏我們能夠寫兩句[dog release]; //也能達到相同的效果。可是,務必不要這樣寫, //我們要遵守內存管理的黃金法則: Dog *dog = [[Dog alloc] init]; // 這是對dog指針進行alloc。須要相應[dog release]; Dog *dog1 = [dog retain]; //這是對dog1指針進行retain,須要相應[dog1 retain]; //這時候打印dog的retainCount是錯誤的使用方法!! //由於對象已經銷毀了!! 對一個已經銷毀的對象發送消息是邏輯錯誤的! //會造成程序的崩潰, //由於dog對象已經銷毀了。沒法調用dog對象的方法。 //註意。假設上面不加兩行打印的話,可能不會崩潰。 NSLog(@"dog retainCount = %lu",[dog retainCount]); } NSLog(@"程序即將退出"); return 0; }
3.3 類的復合中使用
在上面代碼中,添加Person類
@interface Person : NSObject { // 一個人。養了一條狗 Dog *_dog; } - (void)setDog:(Dog *)dog; - (Dog *)dog; @end
setDog方法形式:
@implementation Person /* 人並沒有真正持有狗, 假設在main函數裏[dog release],讓dog的引用計數減1。就變為0, dog就銷毀了。 - (void)setDog:(Dog *)dog { _dog = dog; } */ /* 假設人再持有別的狗。 就會造成第一條狗得不到釋放,內存泄露。 - (void)setDog:(Dog *)dog { _dog = [dog retain]; } */ /* 假設本來持有一條狗,又又一次設置這條狗,先進行release, 這個時候。非常可能dog就銷毀了,然後,就沒法再次retain了。- (void)setDog:(Dog *)dog { [_dog release]; _dog = [dog retain]; } */ // 標準寫法 - (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]; } @end
錯誤方法分析:
//第一個setDog:方法相應的錯誤 Dog *xiaoBai = [[Dog alloc] init]; Person *xiaoXin = [[Person alloc] init]; [xiaoXin setDog:xiaoBai]; //引用計數為1 NSLog(@"count = %lu",xiaoBai.retainCount); [xiaoBai release]; //此時狗已經銷毀了。因此,xiaoXin須要持有這條狗。 [xiaoXin release]; // 第二個setDog:方法相應的錯誤 Dog *xiaoBai = [[Dog alloc] init]; Person *xiaoXin = [[Person alloc] init]; [xiaoXin setDog:xiaoBai]; //引用計數為2 NSLog(@"count = %lu",xiaoBai.retainCount); [xiaoBai release]; Dog *xiaoHei = [[Dog alloc] init]; [xiaoXin setDog:xiaoHei]; [xiaoHei release]; [xiaoXin release]; //此時xiaoBai這條狗沒有釋放 //第三個setDog:方法相應的錯誤 Dog *xiaoBai = [[Dog alloc] init]; Person *xiaoXin = [[Person alloc] init]; [xiaoXin setDog:xiaoBai]; //引用計數為2 NSLog(@"count = %lu",xiaoBai.retainCount); [xiaoBai release]; //這樣設置是不正確的,由於在setDog:裏。將dog進行release的時候, //引用計數為0,dog就銷毀了,無法再retain了。[xiaoXin setDog:xiaoBai]; [xiaoXin release]; //另外,這裏還要說明,類裏。類外,都須要遵守內存管理。
3.4 @property retain,assign。copy展開
i.) retain展開
如上代碼裏。Person的setter和getter方法,也能夠用property,寫成例如以下形式:
@property (nonatomic, retain) Dog *dog;
進行如上測試,都沒有問題。
因此,[email protected] (nonatomic, retain) Dog *dog;,
則會展開例如以下:
- (void)setDog:(Dog *)dog { if (_dog != dog) { [_dog release]; _dog = [dog retain]; } } - (Dog *)dog { return _dog; }
ii.) assign展開
@property (nonatomic, assign) Dog *dog;,assign是直接復制,
則會展開例如以下:
- (void)setDog:(Dog *)dog { _dog = dog; } - (Dog *)dog { return _dog; }
iii.) copy展開
@property (nonatomic, copy) Dog *dog;。copy,拷貝。
將原來的對象拷貝一份出來,展開例如以下:
- (void)setDog:(Dog *)dog { if (_dog != dog) { [_dog release]; _dog = [dog copy]; } } - (Dog *)dog { return _dog; }
3.5 字符串內存管理
i.) 字符串的內存管理
對於字符串而言。很不遵守黃金法則! (假設從字符串的引用計數來看,亂七八糟!)這僅僅是一個表象! 事實上內部還是遵循的!!
我們要做的是,我們依然遵守我們的黃金法則!
NSString *str = [[NSString alloc] initWithFormat:@"%d %s",1,"hello"]; NSLog(@"count1 = %lu",str.retainCount); NSString *str2 = @"hello"; NSLog(@"count2 = %lu",str2.retainCount); NSString *str3 = [str retain]; NSString *str4 = [str2 retain]; NSLog(@"count3 = %lu",str.retainCount); NSLog(@"count4 = %lu",str2.retainCount); NSString *str5 = [[NSString alloc] initWithString:str]; NSString *str6 = [[NSString alloc] initWithString:str2]; NSLog(@"count5 = %lu",str5.retainCount); NSLog(@"count6 = %lu",str6.retainCount); // NSString *str7 = [NSString stringWithFormat:@"%d",5]; [str release]; [str3 release]; [str4 release]; [str5 release]; [str6 release]; // str7不用release!!
因此。假設是NSString,我們的property格式寫成例如以下: @property (nonatomic, copy) NSString *name;
ii.) copy和mutableCopy
NSMutableString *string = [[NSMutableString alloc] initWithString:@"hello"]; // 這樣寫會崩潰。看對象。不看指針 // copy 將(可變或不可變)字符串拷貝成不可變字符串,string2 實際是不可變字符串 // NSMutableString *string2 = [string copy]; // [string2 appendString:@"world"]; NSString *string2 = [string copy]; NSLog(@"string2 = %@",string2); // mutableCopy 將(可變或不可變)字符串拷貝成可變字符串 NSMutableString *string3 = [string2 mutableCopy]; [string3 appendString:@"world"]; NSLog(@"string3 = %@",string3); // 不用管它的引用計數是多少,我們遵守我們自己的黃金法則就夠了 [string release]; [string2 release]; [string3 release]; // UI裏,也不要隨便打印retainCount, 各人顧各人 // new 相當於alloc init,在OC或IOS裏差點兒不用!!
3.6 數組的內存管理
int main(int argc, const char * argv[]) { @autoreleasepool { Dog *dog1 = [[Dog alloc] init]; Dog *dog2 = [[Dog alloc] init]; Dog *dog3 = [[Dog alloc] init]; Dog *dog4 = [[Dog alloc] init]; NSLog(@"dog1.retainCount = %lu",dog1.retainCount); NSMutableArray *array = [NSMutableArray arrayWithObjects:dog1, dog2, dog3, dog4, nil]; NSLog(@"dog1.retainCount = %lu",dog1.retainCount); [array addObject:dog1]; NSLog(@"dog1.retainCount = %lu",dog1.retainCount); // NSLog(@"array = %@",array); [array removeLastObject]; NSLog(@"dog1.retainCount = %lu",dog1.retainCount); // 新的array不是我們alloc new... 的。我們不須要release // [array release]; NSLog(@"dog1.retainCount = %lu",dog1.retainCount); [dog1 release]; [dog2 release]; [dog3 release]; [dog4 release]; } //for @autoreleasepool return 0; }
結論
1)當我們創建數組的時候,數組會對每一個對象進行引用計數加1
2)當數組銷毀的時候,數組會對每一個對象進行引用計數減1
3)當我們給數組加入對象的時候,會對對象進行引用計數加1
4)當我們給數組刪除對象的時候,會對對象進行引用計數減1
總之,誰汙染誰治理,管好自己就能夠了。
3.7 autorelease與 autoreleasepool
autoreleasepool是一個對象
autorelease 是一個方法
在main函數裏寫例如以下代碼:
int main(int argc, const char * argv[]) { @autoreleasepool { Dog *dog = [[Dog alloc] init]; //dog並沒有立即銷毀,而是延遲銷毀, //將dog對象的擁有權交給了autoreleasepool [dog autorelease]; //這個是能夠打印的,由於打印完dog的引用計數後。 //dog對象才銷毀 @autoreleasepool{ [dog autorelease] } NSLog(@"retainCount = %lu",dog.retainCount); } NSLog(@"程序即將退出"); return 0; }
註意: autoreleasepool相當於一個數組,假設哪個對象發送autorelease消息。實際將對象的擁有權交給了autoreleasepool;當autoreleasepool銷毀的時候,將向autoreleasepool裏持有的全部對象都發送一個release消息。
3.8 加方法的內存管理
我們用加方法創建的對象。不用我們release,是由於類內部的實現使用了autorelease。延遲釋放。
在Dog類的聲明裏添加一個加方法
+ (id)dog;
在Dog類的實現裏進行實現
+ (id)dog
{
/*註意,這裏不要寫成release。假設是release,那麽剛創建就銷毀了,
使用autorelease,使得將對象的擁有權交給了自己主動釋放池。
僅僅要自己主動釋放池沒有銷毀,dog對象也就不會銷毀。*/
return [[[Dog alloc] init] autorelease];
}
在main函數裏代碼例如以下:
int main(int argc, const char * argv[]) { @autoreleasepool { Dog *dog = [[[Dog alloc] init] autorelease]; Dog *dog1 = [Dog dog]; NSLog(@"count = %lu",dog.retainCount); NSLog(@"count1 = %lu",dog1.retainCount); } }
OC語言--內存管理