天天看點

9、記憶體管理初級

記憶體管理的⽅式

記憶體溢出

iOS給每個應⽤程式提供了⼀定的記憶體,⽤于程式的運⾏。

iPhone 3GS記憶體30M左右,

iPhone 5S 記憶體80M左右。

⼀旦超出記憶體上限,程式就會Crash。

程式中最占記憶體的就是圖⽚、⾳頻、視訊等資源⽂件。

3.5⼨⾮Retina螢幕(320 * 480)放⼀張全屏圖⽚,占⽤位元組數320 * 480 * 4(⼀個像素占4個位元組,存放RGBA),即:600k Bytes。

iPhone 3GS同時讀取60張圖⽚就會crash。

4⼨螢幕(320 * 568 ),實際像素640 * 1136,程式存放⼀張全屏圖⽚,占⽤位元組數640 * 1136 * 4,即2.77M Bytes。

iPhone 5S同時讀取40張圖⽚就會crash。

野指針異常

對象記憶體空間已經被系統回收,仍然使⽤指針操作這塊記憶體。野指針異常是程式crash的主要原因。代碼量越⼤的程式,越難找出出現野指針的位置

記憶體管理的⽅式

垃圾回收(gc)

程式員隻需要開辟記憶體空間,不需要⽤代碼顯⽰地釋

放,系統來判斷哪些空間不再被使⽤,并回收這些記憶體空間,以便再次配置設定。整個回收的過程不需要寫任何代碼,由系統⾃動完成垃圾回收。

Java開發中⼀直使⽤的就是垃圾回收技術

MRC(Manual Reference Count)⼈⼯引⽤計數

記憶體的開辟和釋放都由

程式代碼進⾏控制。相對垃圾回收來說,對記憶體的控制更加靈活,可以在需要釋放的時候及時釋放,對程式員的要求較⾼,程式員要熟悉記憶體管理的機制。

ARC(Auto Reference Count)⾃動引⽤計數

iOS 5.0的編譯器特性,它允許⽤戶隻開辟空間,不⽤去釋放空間。它不是垃圾回收!它的本質還是MRC,隻是編譯器幫程式員預設加了釋放的代碼

引⽤計數機制,影響計數的各個⽅法

C語⾔中,使⽤malloc和free,進⾏堆記憶體的建立和釋放。堆記憶體隻有正在使⽤和銷毀兩種狀态。

實際開發中,可能會遇到,兩個以上的指針使⽤同⼀塊記憶體。C語⾔⽆法記錄記憶體使⽤者的個數。

OC采⽤引⽤計數機制管理記憶體,當⼀個新的引⽤指向對象時,引⽤計數器就遞增,當去掉⼀個引⽤時,引⽤計數就遞減。當引⽤計數到零時,該對象就将釋放占有的資源。

影響引⽤計數的⽅法

+alloc

開辟記憶體空間,讓被開辟的記憶體空間的引⽤計數變為1。

這是由0到1的過程

-retain

引⽤計數加1,如果記憶體空間之前引⽤計數為1,ratain之後變為2,如果引⽤計數是5,retain之後變為6

-copy

把某⼀記憶體區域的内容拷⻉⼀份,拷⻉到新的記憶體空間⾥去,被拷⻉區域的引⽤計數不變,新的記憶體區域的引⽤計數為1

-release

引⽤計數減1,如果記憶體空間之前引⽤計數為4,release之後變為3,如果之前引⽤計數為1,release之後變為0,記憶體被系統回收。

-autorelease

未來的某⼀時刻引⽤計數減1。如果記憶體之前引⽤計

數為4,autorelease之後仍然為4,未來某個時刻會變為3

autoreleasepool

通過autoreleasepool控制autorelease對象的釋放。

向⼀個對象發送autorelease消息,這個對象何時釋放,取決于autoreleasepool。

NSAutoreleasePool *pool= [[NSAutoreleasePool alloc]init];
Person *p = [[Person alloc]init];//retainCount為1
[p retain];//retainCount為2
[p autorelease];//retainCount為2 未來的某個時刻釋放
[pool release];//此時autorelease的對象引⽤計數-1
NSLog(@”%d”,[p retainCount]);//列印結果為1
           

NSAutoreleasePool *pool= [[NSAutoreleasePool alloc]init];和[pool release];就像⼀對括号,[xxx autorelease];必須寫在兩者之間。

[xxx autorelease];出現在了兩者之間,pool就會把接收autorelease的對象給儲存起來(以棧的⽅式,把對象壓⼊棧)

當[pool release];的時候,pool會向之前儲存的對象逐⼀發送release消息(對象出棧,越晚autorelease的對象,越早接收release消息)。

在iOS5之後,不再推薦使⽤NSAutoreleasePool類,使⽤@autoreleasepool{}替代。

之前寫在NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];和[pool release];之間的代碼,需要寫在@autoreleasepool{}的⼤括号⾥。

出了⼤括号,⾃動釋放池才向各個對象發送release消息。

dealloc⽅法

-dealloc是繼承⾃⽗類的⽅法,當對象引⽤計數為0的時候,由對象⾃動調⽤

- (void)dealloc{
    //驗證對象引⽤計數是否降為0
     NSLog(@“%@被銷毀了”,self);
     [super dealloc];
}
           

記憶體管理的基本原則

  • 引⽤計數的增加和減少相等,當引⽤計數降為0之後,不應該再使⽤這塊記憶體空間
  • 凡是使⽤了alloc、retain或者copy讓記憶體的引⽤計數增加了,就需要使⽤release或者autorelease讓記憶體的引⽤計數減少。在⼀段代碼内,增加和減少的次數要相等

記憶體管理的原則

1)原則

隻要還有人在使用某個對象,那麼這個對象就不會被回收; 隻要你想使用這個對象,那麼就應該讓這個對象的引用計數器+1; 當你不想使用這個對象時,應該讓對象的引用計數器-1;

2)誰建立,誰release

(1)如果你通過alloc,new,copy來建立了一個對象,那麼你就必須調用release或者 autorelease方法

(2)不是你建立的就不用你去負責

3)誰retain,誰release

隻要你調用了retain,無論這個對象時如何生成的,你都要調用release

4)總結

有始有終,有加就應該有減。曾經讓某個對象計數器加1,就應該讓其在最後-1.

掌握copy的實作

跟retain不同,⼀個對象想要copy,⽣成⾃⼰的副本,需要實作NSCopying協定,定義copy的細節(如何copy)。如果類沒有接受NSCopying協定⽽給對象發送copy消息,會引起crash

Person.h⽂件

@interface Person : NSObject<NSCopying>//實作NSCopying協定
@property(nonatomic, retain)NSString *name;
@property(nonatomic, assign)int age;
@end
           

Person.m⽂件

@implementation Person
//NSCopying協定必須實作的方法
-(id)copyWithZone:(NSZone *)zone{
    //用[[Person alloc]init]也可以,建議使用以下,好看噻~
    Person *p = [[Person allocWithZone:zone]init];

    //成員變量的指針隻是一個指派作用,指向的是同一塊記憶體,是淺拷貝,位址相同
    /**/
    p.name = self.name;
    p.sex = self.sex;
    p.age = self.age;
    NSLog(@"%p,%p",p.name,self.name);


    //深拷貝,重新配置設定記憶體,将原來記憶體中的内容放到新的記憶體中,位址不同
    /*
    p.name = [[NSString alloc]initWithFormat:@"%@",self.name];
    NSLog(@"%p,%p",p.name,self.name);
    */

    return p;
}
@end
           

main.m檔案

Person *p = [[Person alloc]init];
p.name = @”張三”;
p.age = 20;
Person *p2 = [p copy];//p2是p的副本,屬于淺拷貝
//p2.name與p.name⼀樣。p2.age與p.age⼀樣。
           

繼續閱讀