iOS的內存管理

概述

我們知道在程序運行過程中要創(chuàng)建大量的對象牧抽,在ObjC中對象時存儲在堆中的,系統(tǒng)并不會自動釋放堆中的內存(注意基本類型是由系統(tǒng)自己管理的,放在棧上)绣张。如果一個對象創(chuàng)建并使用后沒有得到及時釋放那么就會占用大量內存。所以它的內存管理就需要由開發(fā)人員手動維護绎狭,iOS中的內存管理有3中方式:

  • 引用計數(shù)器
  • 屬性參數(shù)
  • 自動釋放池
引用計數(shù)器

在Xcode4.2及之后的版本中由于引入了ARC(Automatic Reference Counting)機制,程序編譯時Xcode可以自動給你的代碼添加內存釋放代碼细溅,如果編寫手動釋放代碼Xcode會報錯,因此儡嘶,你必須手動關閉ARC喇聊,這樣才有助于你理解ObjC的內存回收機制。

ObjC中的內存管理機制跟C語言中指針的內容是同樣重要的蹦狂,要開發(fā)一個程
序并不難誓篱,但是優(yōu)秀的程序則更測重于內存管理,它們往往占用內存更少凯楔,
運行更加流暢窜骄。雖然在新版Xcode引入了ARC,但是很多時候它并不能完全
解決你的問題摆屯。在Xcode中關閉ARC:項目屬性—Build Settings--搜索
“garbage”找到Objective-C Automatic Reference Counting
設置為No即可邻遏。

ObjC中內存的管理是依賴對象引用計數(shù)器來進行的:在ObjC中每個對象內部都有一個與之對應的整數(shù)(retainCount),叫“引用計數(shù)器”虐骑,當一個對象在創(chuàng)建之后它的引用計數(shù)器為1准验,當調用這個對象的alloc、retain廷没、new糊饱、copy方法之后引用計數(shù)器自動在原來的基礎上加1(ObjC中調用一個對象的方法就是給這個對象發(fā)送一個消息),當調用這個對象的release方法之后它的引用計數(shù)器減1颠黎,如果一個對象的引用計數(shù)器為0另锋,則系統(tǒng)會自動調用這個對象的dealloc方法來銷毀這個對象。

下面通過一個簡單的MRC下的例子看一下引用計數(shù)器的知識:
Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject

#pragma mark - 屬性
@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int age;

@end

Person.m

#import "Person.h"

@implementation Person

#pragma mark - 覆蓋方法
#pragma mark 重寫dealloc方法盏缤,在這個方法中通常進行對象釋放操作
-(void)dealloc{
    NSLog(@"Invoke Person's dealloc method.");
    [super dealloc];//注意最后一定要調用父類的dealloc方法(兩個目的:一是父類可能有其他引用對象需要釋放砰蠢;二是:當前對象真正的釋放操作是在super的dealloc中完成的)
}

@end

main.m

#import <Foundation/Foundation.h>
#import "Person.h"

void Test1(){
    Person *p=[[Person alloc]init]; //調用alloc,引用計數(shù)器+1
    p.name=@"Kenshin";
    p.age=28;
    
    NSLog(@"retainCount=%lu",[p retainCount]);
    //結果:retainCount=1
    
    [p release];
    //結果:Invoke Person's dealloc method.
    
    
    
    //上面調用過release方法唉铜,p指向的對象就會被銷毀台舱,但是此時變量p中還存放著Person對象的地址,
    //如果不設置p=nil,則p就是一個野指針竞惋,它指向的內存已經(jīng)不屬于這個程序柜去,因此是很危險的
    p=nil;
    //如果不設置p=nil,此時如果再調用對象release會報錯拆宛,但是如果此時p已經(jīng)是空指針了嗓奢,
    //則在ObjC中給空指針發(fā)送消息是不會報錯的
    [p release];
}

void Test2(){
    Person *p=[[Person alloc]init];
    p.name=@"Kenshin";
    p.age=28;
    
    NSLog(@"retainCount=%lu",[p retainCount]);
    //結果:retainCount=1
    
    [p retain];//引用計數(shù)器+1
    NSLog(@"retainCount=%lu",[p retainCount]);
    //結果:retainCount=2
    
    [p release];//調用1次release引用計數(shù)器-1
    NSLog(@"retainCount=%lu",[p retainCount]);
    //結果:retainCount=1
    [p release];
    //結果:Invoke Person's dealloc method.
    p=nil;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Test1();
    }
    return 0;
}

在上面的代碼中我們可以通過dealloc方法來查看是否一個對象已經(jīng)被回收,如果沒有被回收則有可能造成內存泄露浑厚。如果一個對象被釋放之后股耽,那么最后引用它的變量我們手動設置為nil,否則可能造成野指針錯誤钳幅,而且需要注意在ObjC中給空對象發(fā)送消息是不會引起錯誤的物蝙。

內存釋放的原則

手動管理內存有時候并不容易,因為對象的引用有時候是錯綜復雜的敢艰,對象之間可能互相交叉引用诬乞,此時需要遵循一個法則:誰創(chuàng)建,誰釋放钠导。

假設現(xiàn)在有一個人員Person類震嫉,每個Person可能會購買一輛汽車Car,通常情況下購買汽車這個活動我們可能會單獨抽取到一個方法中牡属,同時買車的過程中我們可能會多看幾輛來最終確定理想的車票堵,現(xiàn)在我們的代碼如下:

Car.h

#import <Foundation/Foundation.h>

@interface Car : NSObject

#pragma mark - 屬性
#pragma mark 車牌號
@property (nonatomic,copy) NSString *no;

#pragma mark - 公共方法
#pragma mark 運行方法
-(void)run;

@end

Car.m

#import "Car.h"

@implementation Car

#pragma mark - 公共方法
#pragma mark 運行方法
-(void)run{
    NSLog(@"Car(%@) run.",self.no);
}

#pragma mark - 覆蓋方法
#pragma mark 重寫dealloc方法
-(void)dealloc{
    
    NSLog(@"Invoke Car(%@) dealloc method.",self.no);
    [super dealloc];
}
@end

Person.h

#import <Foundation/Foundation.h>
@class Car;

@interface Person : NSObject{
    Car *_car;
}

#pragma mark - 屬性
#pragma mark 姓名
@property (nonatomic,copy) NSString *name;

#pragma mark - 公共方法
#pragma mark Car屬性的set方法
-(void)setCar:(Car *)car;
#pragma mark  Car屬性的get方法
-(Car *)car;
@end

Person.m

#import "Person.h"
#import "Car.h"

@implementation Person

#pragma mark - 公共方法
#pragma mark Car屬性的set方法
-(void)setCar:(Car *)car{
    if (_car!=car) { //首先判斷要賦值的變量和當前成員變量是不是同一個變量
        [_car release]; //釋放之前的對象
        _car=[car retain];//賦值時重新retain
    }
}
#pragma mark  Car屬性的get方法
-(Car *)car{
    return _car;
}

#pragma mark - 覆蓋方法
#pragma mark 重寫dealloc方法
-(void)dealloc{
    NSLog(@"Invoke Person(%@) dealloc method.",self.name);
    [_car release];//在此釋放對象,即使沒有賦值過由于空指針也不會出錯
    [super dealloc];
}
@end

main.m

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"

void getCar(Person *p){
    Car *car1=[[Car alloc]init];
    car1.no=@"888888";
    
    p.car=car1;
    
    NSLog(@"retainCount(p)=%lu",[p retainCount]);
    
    Car *car2=[[Car alloc]init];
    car2.no=@"666666";
    
    [car1 release];
    car1=nil;
    
    [car2 release];
    car2=nil;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p=[[Person alloc]init];
        p.name=@"Kenshin";
        getCar(p); 
        [p.car run];
        [p release];
        p=nil;      
    }
    return 0;
}
Snip20160624_3.png

從運行結果來看創(chuàng)建的三個對象p湃望、car1换衬、car2都被回收了,而且[p.car run]也能順利運行证芭,已經(jīng)達到了我們的需求

因為在setCar方法中我們通過[car retain]保證每次屬性賦值的時候對象引用計數(shù)器+1,這樣一來調用過getCar方法可以保證人員的car屬性不會被釋放瞳浦,其次為了保證上一次的賦值對象(car1)能夠正常釋放,我們在賦新值之前對原有的值進行release操作废士。最后在Person的dealloc方法中對_car進行一次release操作(因為setCar中做了一次retain操作)保證_car能正辰辛剩回收。

屬性參數(shù)

像上面這樣編寫setCar方法的情況是比較多的官硝,那么如何使用@property進行自動實現(xiàn)呢矗蕊?答案就是使用屬性參數(shù),例如上面car屬性的setter方法氢架,可以通過@property定義如下:

@property (nonatomic,retain) Car *car;

你會發(fā)現(xiàn)此刻我們不必手動實現(xiàn)car的getter傻咖、setter方法程序仍然沒有內存泄露。其實大家也應該都已經(jīng)看到前面Person的name屬性定義的時候我們同樣加上了(nonatomic,copy)參數(shù)岖研,這些參數(shù)到底是什么意思呢卿操?

Snip20160624_4.png

@property的參數(shù)分為三類警检,也就是說參數(shù)最多可以有三個,中間用逗號分隔害淤,每類參數(shù)可以從上表三類參數(shù)中人選一個扇雕。如果不進行設置或者只設置其中一類參數(shù),程序會使用三類中的各個默認參數(shù)窥摄,默認參數(shù):(atomic,readwrite,assign)

一般情況下如果在多線程開發(fā)中一個屬性可能會被兩個及兩個以上的線程同時訪問镶奉,此時可以考慮atomic屬性,否則建議使用nonatomic崭放,不加鎖哨苛,效率較高;readwirte方法會生成getter币砂、setter兩個方法移国,如果使用readonly則只生成getter方法;關于set方法處理需要特別說明道伟,假設我們定義一個屬性a,這里列出三種方式的生成代碼:

assign使碾,用于基本數(shù)據(jù)類型

-(void)setA:(int)a{
    _a=a;
}

retain蜜徽,通常用于非字符串對象,ARC下用Strong

-(void)setA:(Car *)a{
    if(_a!=a){
        [_a release];
        _a=[a retain];
    }
}

copy,通常用于字符串對象票摇、block拘鞋、NSArray、NSDictionary

-(void)setA:(NSString *)a{
    if(_a!=a){
        [_a release];
        _a=[a copy];
    }
}
自動釋放池

在ObjC中也有一種內存自動釋放的機制自動釋放池,自動內存釋放使用@autoreleasepool關鍵字聲明一個代碼塊矢门,如果一個對象在初始化時調用了autorelase方法盆色,那么當代碼塊執(zhí)行完之后,在塊中調用過autorelease方法的對象都會自動調用一次release方法祟剔。這樣一來就起到了自動釋放的作用隔躲,同時對象的銷毀過程也得到了延遲(統(tǒng)一調用release方法)∥镅樱看下面的代碼:

person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject

#pragma mark - 屬性
#pragma mark 姓名
@property (nonatomic,copy) NSString *name;

#pragma mark - 公共方法
#pragma mark 帶參數(shù)的構造函數(shù)
-(Person *)initWithName:(NSString *)name;
#pragma mark 取得一個對象(靜態(tài)方法)
+(Person *)personWithName:(NSString *)name;
@end

person.m

#import "Person.h"

@implementation Person

#pragma mark - 公共方法
#pragma mark 帶參數(shù)的構造函數(shù)
-(Person *)initWithName:(NSString *)name{
    if(self=[super init]){
        self.name=name;
    }
    return self;
}
#pragma mark 取得一個對象(靜態(tài)方法)
+(Person *)personWithName:(NSString *)name{
    Person *p=[[[Person alloc]initWithName:name] autorelease];//注意這里調用了autorelease
    return p;
}

#pragma mark - 覆蓋方法
#pragma mark 重寫dealloc方法
-(void)dealloc{
    NSLog(@"Invoke Person(%@) dealloc method.",self.name);
    [super dealloc];
}

@end

main.m

#import <Foundation/Foundation.h>
#import "Person.h"


int main(int argc, const char * argv[]) {

    @autoreleasepool {
        Person *person1=[[Person alloc]init];
        [person1 autorelease];//調用了autorelease方法后面就不需要手動調用release方法了
        person1.name=@"Kenshin";//由于autorelease是延遲釋放宣旱,所以這里仍然可以使用person1
        
        Person *person2=[[[Person alloc]initWithName:@"Kaoru"] autorelease];//調用了autorelease方法
        
        Person *person3=[Person personWithName:@"rosa"];//內部已經(jīng)調用了autorelease,所以不需要手動釋放叛薯,這也符合內存管理原則浑吟,因為這里并沒有alloc所以不需要release或者autorelease
        
        Person *person4=[Person personWithName:@"jack"];
        [person4 retain];
    }
    /*結果:
     Invoke Person(rosa) dealloc method.
     Invoke Person(Kaoru) dealloc method.
     Invoke Person(Kenshin) dealloc method.
     */
    
    return 0;
}

當上面@autoreleaespool代碼塊執(zhí)行完之后,三個對象都得到了釋放耗溜,但是person4并沒有釋放组力,原因很簡單,由于我們手動retain了一次抖拴,當自動釋放池釋放后調用四個對的release方法燎字,當調用完person4的release之后它的引用計數(shù)器為1,所有它并沒有釋放(這是一個反例,會造成內存泄露)轩触;autorelase方法將一個對象的內存釋放延遲到了自動釋放池銷毀的時候寞酿,因此上面person1,調用完autorelase之后它還存在脱柱,因此給name賦值不會有任何問題伐弹;在ObjC中通常如果一個靜態(tài)方法返回一個對象本身的話,在靜態(tài)方法中我們需要調用autorelease方法榨为,因為按照內存釋放原則惨好,在外部使用時不會進行alloc操作也就不需要再調用release或者autorelase,所以這個操作需要放到靜態(tài)方法內部完成随闺。

對于自動內存釋放簡單總結一下:

1.autorelease方法不會改變對象的引用計數(shù)器日川,只是將這個對象放到自動釋放池中;
2.自動釋放池實質是當自動釋放池銷毀后調用對象的release方法矩乐,不一定就能銷毀對象(例如如果一個對象的引用計數(shù)器>1則此時就無法銷毀)龄句;
3.由于自動釋放池最后統(tǒng)一銷毀對象,因此如果一個操作比較占用內存(對象比較多或者對象占用資源比較多)散罕,最好不要放到自動釋放池或者考慮放到多個自動釋放池分歇;
4.ObjC中類庫中的靜態(tài)方法一般都不需要手動釋放,內部已經(jīng)調用了autorelease方法欧漱;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末职抡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子误甚,更是在濱河造成了極大的恐慌缚甩,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窑邦,死亡現(xiàn)場離奇詭異擅威,居然都是意外死亡,警方通過查閱死者的電腦和手機冈钦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門裕寨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人派继,你說我怎么就攤上這事宾袜。” “怎么了驾窟?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵庆猫,是天一觀的道長。 經(jīng)常有香客問我绅络,道長月培,這世上最難降的妖魔是什么嘁字? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮杉畜,結果婚禮上纪蜒,老公的妹妹穿的比我還像新娘。我一直安慰自己此叠,他們只是感情好纯续,可當我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著灭袁,像睡著了一般猬错。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上茸歧,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天倦炒,我揣著相機與錄音,去河邊找鬼软瞎。 笑死逢唤,一個胖子當著我的面吹牛,可吹牛的內容都是我干的涤浇。 我是一名探鬼主播智玻,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼芙代!你這毒婦竟也來了?” 一聲冷哼從身側響起盖彭,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤纹烹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后召边,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铺呵,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年隧熙,在試婚紗的時候發(fā)現(xiàn)自己被綠了片挂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡贞盯,死狀恐怖音念,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情躏敢,我是刑警寧澤闷愤,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站件余,受9級特大地震影響讥脐,放射性物質發(fā)生泄漏遭居。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一旬渠、第九天 我趴在偏房一處隱蔽的房頂上張望俱萍。 院中可真熱鬧,春花似錦告丢、人聲如沸枪蘑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽腥寇。三九已至,卻和暖如春觅捆,著一層夾襖步出監(jiān)牢的瞬間赦役,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工栅炒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留掂摔,地道東北人。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓赢赊,卻偏偏與公主長得像乙漓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子释移,可洞房花燭夜當晚...
    茶點故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內容

  • 一.內存管理的原因 (1)內存溢出 內存不夠用 (2)野指針異常 指針操作了不屬于自己的存儲空間叭披,指針操作已經(jīng)銷毀...
    激進的小火車122閱讀 293評論 0 0
  • 今天看到一篇不錯的文章關于OC內存管理的,轉載一下與你共享概述我們知道在程序運行過程中要創(chuàng)建大量的對象,和其他高級...
    niceSYT閱讀 454評論 0 2
  • 1.遠古時代的故事那些經(jīng)歷過手工管理內存(MRC)時代的人們玩讳,一定對 iOS 開發(fā)中的內存管理記憶猶新涩蜘。那個時候大...
    MissHector閱讀 210評論 0 1
  • 引言: ARC的出生及成長背景 蘋果在 2011 年的時候,在 WWDC 大會上提出了自動的引用計數(shù)(ARC)熏纯。A...
    滿臉胡茬的小碼農閱讀 527評論 0 1
  • 前言 通過這段時間的學習同诫,對Objective-C的內存管理知識做一個總結。分享給大家樟澜,如有理解錯誤的地方误窖,還望多...
    WestMiss閱讀 171評論 0 0