可能是史上最全面的內(nèi)存管理文章

neicunguanli.png

iOS內(nèi)存管理

概述

什么是內(nèi)存管理

應(yīng)用程序內(nèi)存管理是在程序運行時分配內(nèi)存(比如創(chuàng)建一個對象,會增加內(nèi)存占用)與清除內(nèi)存(比如銷毀一個對象,會減少內(nèi)存占用)的過程

為什么要管理內(nèi)存

目前iPhone手機內(nèi)存大多為1G,分配給每個應(yīng)用程序的內(nèi)存空間極其有限,當(dāng)應(yīng)用程序運行過程中所占用的內(nèi)存較大時,便會收到系統(tǒng)給出的內(nèi)存警告,如果應(yīng)用程序所占用的內(nèi)存超過限制時,便會被系統(tǒng)強制關(guān)閉,所以我們需要對應(yīng)用程序進行內(nèi)存管理,一個好的程序程序也應(yīng)該盡可能少地占用內(nèi)存

內(nèi)存管理的方式

在OC中提供了兩種管理內(nèi)存的方式

  • MRR(Manual Retain-Release),也被人稱作MRC(Manual Reference Counting,手動引用計數(shù))
  • ARC(Automatic Reference Counting,自動引用計數(shù))

注: 在Build Setting中的Objective-C Automatic Reference Counting設(shè)置為YES即為ARC

內(nèi)存管理的范圍

任何繼承自NSObject的對象都需要管理內(nèi)存(因存放在堆里面),基本數(shù)據(jù)類型int、float闰围、double、char、結(jié)構(gòu)體struct以及枚舉enum都不需要管理內(nèi)存(因存放在棧里面)

  • 堆: 一般由程序員分配釋放內(nèi)存,若程序員不釋放,程序結(jié)束時可能由OS釋放,其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的鏈表
  • 棧: 由操作系統(tǒng)自動分配釋放,存放函數(shù)的參數(shù)值,局部變量值等,其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧(先進后出)
內(nèi)存管理的規(guī)則

內(nèi)存管理模型基于對象所有權(quán)機制,一個對象的所有權(quán)可以擁有一個或者多個擁有者,只要一個對象的所有權(quán)擁有至少一個擁有者,該對象就會一直存在于內(nèi)存中.如果一個對象的所有權(quán)沒有任何擁有者,那么系統(tǒng)便會自動地將該對象所占用的內(nèi)存釋放掉.為了明確何時擁有某個對象的所有權(quán)幌衣、何時放棄某個對象的所有權(quán),系統(tǒng)設(shè)置了如下規(guī)則

  • 你擁有你創(chuàng)建的對象的所有權(quán): 你擁有使用alloc,new,copy或者mutableCopy方法創(chuàng)建的新對象的所有權(quán)
  • 你可以通過retain來獲取一個對象的所有權(quán): 在兩種情形中會使用到retain來獲取一個對象的所有權(quán)
    • 在init方法和setter方法中,使用retain來獲取一個想要保存為屬性值的對象的所有權(quán)
    • 防止一個對象在一些操作中被設(shè)置為無效,具體參見"內(nèi)存管理中需要注意的兩個使用場景"
  • 你必須放棄你不再需要擁有其所有權(quán)的對象的所有權(quán): 你必須通過release或者autorelease方法來放棄你不再需要擁有其所有權(quán)的對象的所有權(quán),在Cocoa術(shù)語中"放棄一個對象的所有權(quán)"一般被稱作"釋放一個對象"
  • 你不能放棄一個你不擁有的對象的所有權(quán): 這個規(guī)則是以上幾點規(guī)則的推論

iOS內(nèi)存管理之MRR&MRC

概述

在iOS5以前,程序員普遍使用MRR&MRC方式來管理內(nèi)存,程序員需要自己添加retain、release和autorelease等內(nèi)存管理代碼來跟蹤自己所擁有的對象以明確地管理對象的內(nèi)存,在需要使用該對象的時候保證該對象一直存在于內(nèi)存中,在不需要使用該對象的時候保證該對象所占用的內(nèi)存被系統(tǒng)正吃不耍回收

為了讓系統(tǒng)知道何時需要將某個對象所占用的內(nèi)存清理掉,系統(tǒng)引入了引用計數(shù)器的概念

引用計數(shù)器

概述

系統(tǒng)為每個OC對象內(nèi)部都分配了4個字節(jié)的存儲空間存放自己的引用計數(shù)器,引用計數(shù)器是一個整數(shù),表示“對象被引用的次數(shù)”,當(dāng)對象的引用計數(shù)器為0時,對象所占用的內(nèi)存空間就會被系統(tǒng)自動回收,當(dāng)對象的引用計數(shù)器不為0時,在程序運行過程中所占用的內(nèi)存會一直存在,直到整個程序退出時由OS自動釋放

操作引用計數(shù)器
  • 當(dāng)使用alloc远舅、new、copy僵控、mutableCopy創(chuàng)建一個新對象時,該新對象的引用計數(shù)器為1
  • 當(dāng)給對象發(fā)送一條retain消息時,對象的引用計數(shù)器+1(方法返回對象本身)
  • 當(dāng)給對象發(fā)送一條release消息時對象的引用計數(shù)器-1(方法無返回值)
  • 當(dāng)給對象發(fā)送一條retainCount消息時,返回對象的當(dāng)前引用計數(shù)器(不要以該數(shù)據(jù)來判斷對象是否被釋放)

注: 給對象發(fā)送一條release消息,不代表對象會被釋放,只有對象的引用計數(shù)器為0時才會被釋放

示例

下面通過一個小示例,來演示一下如何操作對象的引用計數(shù)器

Person *p = [[Person alloc] init]; // 使用alloc創(chuàng)建一個新對象,對象引用計數(shù)器 = 1
[p retain]; // 給對象發(fā)送一條retain消息,對象引用計數(shù)器 + 1 = 2
[p release]; // 給對象發(fā)送一條release消息,對象引用計數(shù)器 - 1 = 1
[p release]; // 給對象發(fā)送一條release消息,對象引用計數(shù)器 - 1 = 0,指針?biāo)赶虻膶ο蟮膬?nèi)存被釋放

僵尸對象香到、野指針與空指針

概述

下面介紹幾個關(guān)于內(nèi)存管理方面常常提到的術(shù)語

  • 僵尸對象: 所占用的內(nèi)存已經(jīng)被回收的對象,僵尸對象不能再使用
  • 野指針: 指向僵尸對象的指針,給野指針發(fā)送消息會報錯EXC_BAD_ACCESS錯誤:訪問了一塊已經(jīng)被回收的內(nèi)存
  • 空指針: 沒有指向任何對象的指針(存儲的東西是nil,NULL,0),給空指針發(fā)送消息不會報錯,系統(tǒng)什么也不會做,所以在對象被釋放時將指針設(shè)置為nil可以避免野指針錯誤

注: 默認情況下,Xcode是不會監(jiān)聽僵尸對象的,所以需要我們自己手動開啟,開啟監(jiān)聽僵尸對象步驟為: Edit Scheme ->; Run ->; Diagnostics ->; Objective-C的Enable Zombie Objects打鉤,這樣便可以在因為僵尸對象報錯的時候給出更多錯誤信息

示例
Person *p = [[Person alloc] init]; // 引用計數(shù)器 = 1
[p release]; // 引用計數(shù)器 - 1 = 0,指針?biāo)赶虻膶ο蟮膬?nèi)存被釋放
[p release]; // 這句給野指針發(fā)送消息,會報野指針錯誤,開啟監(jiān)聽僵尸對象會給出錯誤信息**- -[Person release]: message sent to deallocated instance 0x100206fd0

注: 如果在第一次給對象發(fā)送release消息后,立刻將指針置空,便不會出現(xiàn)野指針錯誤,因為給空指針發(fā)送消息不會報錯,系統(tǒng)什么也不會做,所以在對象被釋放時將指針設(shè)置為nil可以避免野指針錯誤

單對象內(nèi)存管理

概述

單對象的內(nèi)存管理比較簡單,當(dāng)使用alloc、new报破、copy悠就、mutableCopy創(chuàng)建一個新對象時,該新對象的引用計數(shù)器為1,我們只需要在對象不再使用的時候給其發(fā)送一次release消息,讓對象的引用計數(shù)器-1變?yōu)?即可正常釋放

示例

下面通過一個小示例,來演示一下如何對單對象進行內(nèi)存管理

// 在堆中開辟一塊內(nèi)存存放Person對象,在棧中開辟一塊內(nèi)存存儲局部變量p,p中存放的是Person對象的地址
// 當(dāng)出了大括號,局部變量p出了作用域便會被系統(tǒng)自動回收,而Person對象引用計數(shù)器為1仍然存在,這樣便造成內(nèi)存的泄漏,所以我們需要在局部變量p被系統(tǒng)回收之前,給p所指向的Person對象發(fā)送一條release消息讓對象引用計數(shù)器-1變?yōu)?
Person *p = [[Person alloc] init];
[p release];

多對象內(nèi)存管理

概述

相對于單對象內(nèi)存管理而言,多對象內(nèi)存管理顯得比較復(fù)雜,但是只要我們遵從基本的內(nèi)存管理規(guī)則,即可避免大部分內(nèi)存管理相關(guān)的問題

  • 當(dāng)A對象想要擁有B對象的所有權(quán)時,一定要對B對象進行一次retain操作,這樣才能保證A對象存在期間B對象一直存在
  • 當(dāng)A對象想要放棄對B對象的所有權(quán)時,一定要對B對象進行一次release操作,這樣才能保證A對象釋放了,B對象也能正常釋放
存取器方法

在涉及到多對象內(nèi)存管理時,平時我們使用的存取器方法便需要針對內(nèi)存管理進行一些調(diào)整

  • getter方法規(guī)范: 直接返回實例變量
// 針對基本數(shù)據(jù)類型
- (int)age
{
    return _age;
}

// 針對OC對象
- (Room *)room
{
    return _room;
}
  • setter方法規(guī)范:
// 針對基本數(shù)據(jù)類型,直接將新值賦值給實例變量
- (void)setAge:(int)age
{
    _age = age;
}

// 針對OC對象,方式一: 判斷兩次傳入的對象是否相同,如果不同便release舊對象retain新對象,并將新對象賦值給實例變量
- (void)setRoom:(Room *)room
{
    if (_room != room)
    {
        [_room release];
        _room = [room retain];
    }
}

// 針對OC對象,方式二: retain新對象,release舊對象,并將新對象賦值給實例變量
- (void)setRoom:(Room *)room
{
    [room retain];
    [_room release];
    _room = room;
}

注1: release舊對象retain新對象原因: 避免設(shè)置兩次不同的room,在Person對象釋放時,第一次設(shè)置的對象無法被釋放,造成內(nèi)存泄露

注2: 判斷當(dāng)前對象與傳入對象是否相同原因: 避免設(shè)置兩次相同的room,在設(shè)置第二次時,將room已經(jīng)釋放了,再調(diào)用retain造成野指針錯誤

dealloc方法

當(dāng)系統(tǒng)回收對象的內(nèi)存時,系統(tǒng)會自動給該對象發(fā)送一條dealloc消息,我們一般會重寫dealloc方法,在這里給當(dāng)前對象所擁有的資源(包括實例變量)發(fā)送一條release消息(基本數(shù)據(jù)類型不用),保證自身所擁有的資源也可以正常釋放(因為在使用該資源的時候,采用retain獲取了該資源的所有權(quán),在自身釋放的同時,也應(yīng)該放棄對該資源的所有權(quán))

- (void)dealloc
{
    NSLog(@"Person dealloc");

    // release對象所擁有資源
    [_room release];
    // 設(shè)置為nil可以避免野指針錯誤(其實可以不設(shè)置,只是寫了顯得有逼格)
    _room = nil;

    [super dealloc];
}

注1: 不要直接調(diào)用對象的dealloc方法

注2: 重寫dealloc方法時,一定要調(diào)用[super dealloc]方法,且放在代碼的最后

注3: 當(dāng)應(yīng)用程序被關(guān)掉,dealloc方法不一定會被調(diào)用,因為由系統(tǒng)OS直接來釋放內(nèi)存比調(diào)用dealloc釋放內(nèi)存效率得多

注4: 不要在dealloc方法中管理稀缺資源(比如網(wǎng)絡(luò)連接,文件資源,DOS命令等),因為dealloc并不一定都是立即調(diào)用,有可能會延遲調(diào)用,也可能根本不會被調(diào)用

屬性@property

概述

屬性可以幫助我們免除重復(fù)地寫存取器代碼的麻煩,并且合理使用屬性修飾符也可以幫助我們生成符合內(nèi)存管理規(guī)范的代碼

兩個編譯器指令: @property和@synthesize

在Xcode4.4之前

  • @property用于替代setter和getter方法的聲明
@property double weight;

- (void)setWeight:(double)weight;
- (double)weight;
  • @synthesize用于替代setter和getter方法的實現(xiàn)
@synthesize weight = _weight;

- (void)setWeight:(double)weight
{
    _weight = weight;
}

- (double)weight
{
    return _weight;
}

注1: 在@synthesize后面告訴編譯器,將要實現(xiàn)哪個@property的實現(xiàn),在等號后邊告訴編譯器,將setter傳進來的值賦值給誰和getter返回誰的值給調(diào)用者

注2: 在@synthesize中如果不寫等號后面的,系統(tǒng)會默認將setter傳進來的值賦值給與@synthesize后面的變量同名的成員變量,且getter返回與@synthesize后面的變量同名的成員變量的值給調(diào)用者,即weight而不是_weight

在Xcode4.4之后

  • @property可以直接替代setter和getter方法的聲明和實現(xiàn),同時會生成以下劃線開頭的"私有"成員變量(即在.m文件@implementation下面{}中生成的,在其他類中不能直接訪問)

注: 不用寫@synthesize了,系統(tǒng)會默認為將setter傳進來的值賦值以下劃線開頭的成員變量和getter返回以下劃線開頭的成員變量給調(diào)用者

  • @property只會生成最簡單的setter和getter方法的聲明和實現(xiàn),并不會對傳入的數(shù)據(jù)進行過濾,如果想對傳入的數(shù)據(jù)進行過濾,可以自己重寫setter和getter方法的實現(xiàn)
    • 如果重寫了setter方法的實現(xiàn),那么@property只會幫忙生成getter方法的實現(xiàn)
    • 如果重寫了getter方法的實現(xiàn),那么@property只會幫忙生成setter方法的實現(xiàn)
    • 如果同時重寫了setter方法和getter方法的實現(xiàn),那么@property就不會幫忙生成以下劃線開頭的"私有"成員變量,需要自己生成

注: 使用@property并不會幫忙實現(xiàn)dealloc中的[_name release];所以仍需要自己釋放對象所擁有的資源

屬性修飾符

屬性修飾符用來修飾@property,給@property添加特定功能,以NSNumber對象為例

  • 如果不寫修飾符(即默認assign),自動生成的setter是直接賦值
@property NSNumber *number;

- (void)setNumber:(NSNumber *)number
{
    _number = number;
}
  • 如果寫了retain修飾符,自動生成的setter便是符合內(nèi)存管理規(guī)則的存取器方法(判斷兩次傳入的對象是否相同,如果不同便release舊對象retain新對象,并將新對象賦值給實例變量)
@property (retain) NSNumber *number;

- (void)setNumber:(NSNumber *)number
{
    if (_number != number)
    {
        [_number release];
        _number = [number retain];
    }
}

在OC中有如下幾類屬性修飾符

  • retain, assign, copy

    • retain自動生成的setter是判斷當(dāng)前對象與傳入對象是否相同,如果不同release舊對象retain新對象(適用于OC對象)
    • assign自動生成的setter是直接賦值(默認,適用于基本數(shù)據(jù)類型)
    • copy:自動生成的setter是判斷當(dāng)前對象與傳入對象是否相同,如果不同release舊對象copy新對象(適用于NSString *,詳見"iOS內(nèi)存管理之Copy")
  • readonly, readwrite

    • readonly只生成getter的聲明和實現(xiàn)
    • readwrite同時生成setter和getter的聲明和實現(xiàn)
  • atomic, nonatomic

    • atomic性能低(默認)
    • nonatomic性能高(iOS開發(fā)中99.99%使用這個)
  • setter, getter

    • setter給生成的setter方法取一個名字,一定要有冒號
    • getter給生成的getter方法取一個名字,一般用于BOOL類型中
// 枚舉
typedef NS_ENUM(NSInteger, Sex)
{
    SexMan,
    SexWoman
};

// 結(jié)構(gòu)體
typedef struct
{
    int year;
    int month;
    int day;
} Date;

@interface Person : NSObject

@property (nonatomic, assign) int age;
@property (nonatomic, assign) Sex sex;
@property (nonatomic, assign) Date birthday;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, retain, readonly) NSString *phone;
@property (nonatomic, assign, getter = isRich) BOOL rich;

@end

@implementation Person

- (void)dealloc
{
    NSLog(@"Person dealloc");

    [_name release];
    [_phone release];

    [super dealloc];
}

@end
使用存取器方法時應(yīng)該注意以下使用場景
  • 如果在類中聲明了一個方法用于設(shè)置實例變量,需要使用存取器方法來設(shè)置
@property (nonatomic, retain) NSNumber *count;

- (void)reset
{
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [self setCount:zero];
    [zero release];
}

// 雖然下述方法也能實現(xiàn),但是這種方式避開了存取器方法,可能會導(dǎo)致一些錯誤(比如實例變量內(nèi)存管理規(guī)則發(fā)生了變化,并且這種方法不符合KVO規(guī)則)
- (void)reset
{
    NSNumber *zero = [[NSNumber alloc] initWithInteger:0];
    [_count release];
    _count = zero;
}
  • 不要在init方法和dealloc方法中使用存取器方法來訪問和修改實例變量
- init 
{
    self = [super init];
    if (self) 
    {
        _count = [[NSNumber alloc] initWithInteger:0];
    }
    return self;
}

- initWithCount:(NSNumber *)startingCount 
{
    self = [super init];
    if (self) 
    {
        _count = [startingCount copy];
    }
    return self;
}

- (void)dealloc 
{
    [_count release];
    [super dealloc];
}

循環(huán)import

#import概述

import是一個預(yù)編譯指令,他會將""中的文件拷貝到import所在的位置,不過一旦""中的文件發(fā)生變化,那么import就會重新拷貝一次,從而降低編譯效率

在開發(fā)中經(jīng)常會出現(xiàn)循環(huán)import的情況,表現(xiàn)形式為在A.h文件中import了B.h文件,在B.h文件中import了A.h文件,那么A.h和B.h兩個文件會相互拷貝,造成循環(huán)import報錯

@class概述

@class ClassName;僅僅是告訴編譯器,ClassName是一個類,不會做任何拷貝操作,不過由于@class僅僅是告訴編譯器ClassName是一個類,并不知道ClassName類中到底有什么方法,所以在.m文件中使用該類時還是要#import該類

使用@class避免循環(huán)import

在開發(fā)中我們可以使用@class來避免循環(huán)import的問題,即在想要使用一個類的時候,可以在.h中使用@class ClassName;來聲明一個類,真正使用這個類的時候在.m中#import "ClassName"即可

注: 在一個ClassName發(fā)生變化時,一般只有import了它的.m類才需要重新拷貝,并不會讓其他類間接受到影響(因為其他類不會引用.m文件)

循環(huán)引用

如果A對象要擁有B對象的所有權(quán),同時B對象也要擁有A對象的所有權(quán),那么就會產(chǎn)生循環(huán)引用,導(dǎo)致兩者都無法被正常釋放

@interface Person : NSObject
@property (nonatomic, retain) Car *car;
@end

@interface Car : NSObject
@property (nonatomic, retain) Person *person;
@end

針對循環(huán)引用的問題,Cocoa建立了一種規(guī)則,就是在出現(xiàn)循環(huán)引用的情形時,不要讓A對象和B對象互相擁有對方的所有權(quán),而是讓一方(parent)擁有另一方(children)的"強"引用,另一方(children)擁有這一方(parent)的"弱"引用,即將A retain B, B retain A中一方的retain改為assign即可

@interface Person : NSObject
@property (nonatomic, retain) Car *car;
@end

@interface Car : NSObject
@property (nonatomic, assign) Person *person;
@end

注: 使用assign之后,dealloc中無需再釋放該資源

在Cocoa中使用到"弱"引用的例子包括但不局限于table data sources, outline view items, notification observers, and miscellaneous targets and delegates,在處理"弱"引用的對象時一定要小心處理,因為parent并不擁有children的所有權(quán),也就是并不能保證在使用children的時候其一定存在(可能已經(jīng)被釋放),所以在parent自身釋放時一定要告知children一下自身已經(jīng)釋放,不要再繼續(xù)給自己發(fā)消息,否則會造成應(yīng)用程序的閃退(野指針錯誤).處理辦法針對通過中心/觀察者而言,便是移除觀察者;針對delegate而言,便是setDelegate:為nil,這個操作一般都是在dealloc中進行

自動釋放池

概述

自動釋放池提供了延遲放棄一個對象的所有權(quán)的機制,比如想要在一個方法中返回一個對象,如果先使用release放棄了該對象的所有權(quán),那么return返回的對象便是一個僵尸對象,如果先進行return返回,那么便無法放棄該對象的所有權(quán),導(dǎo)致了內(nèi)存泄漏

自動釋放池的創(chuàng)建

iOS5.0之前創(chuàng)建自動釋放池方法(現(xiàn)在也可使用)

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// do something...
[pool release];

iOS5.0之后創(chuàng)建自動釋放池方法

@autoreleasepool
{
    // do something...
}
autorelease方法

autorelease是一種支持引用計數(shù)的內(nèi)存管理方式,只要在自動釋放池中給對象發(fā)送一條autorelease消息,就會將對象放到自動釋放池中,當(dāng)自動釋放池被銷毀時,會對池中的所有對象發(fā)送一條release消息

  • autorelease方法會返回對象本身
  • autorelease方法不會修改對象的引用計數(shù)器
  • autorelease方法可以讓開發(fā)者不用實時關(guān)心什么時候發(fā)送release消息

注1: 自動釋放池被銷毀時,只是給池中所有對象發(fā)送一條release消息,不代表對象一定會被釋放

注2: 對象在自動釋放池中每收到一條autorelease消息,在自動釋放池被銷毀時,對象都會收到一次release消息

autorelease方法使用注意事項
  • 一定要在自動釋放池中調(diào)用autorelease方法,才會將對象放入自動釋放池中
  • 即使在自動釋放池內(nèi)創(chuàng)建對象,只要不調(diào)用了autorelease方法,就不會將對象放入自動釋放池中
  • 即使在自動釋放池外創(chuàng)建對象,只要在自動釋放池中調(diào)用了autorelease方法,就會將對象放入自動釋放池中
  • 一個程序中可以創(chuàng)建N個自動釋放池,且自動釋放池可以嵌套,這些自動釋放池以棧結(jié)構(gòu)存在(先進后出),當(dāng)一個對象調(diào)用autorelease方法時,會將這個對象放到棧頂?shù)淖詣俞尫懦刂?/li>
  • autorelease不能精準(zhǔn)地釋放內(nèi)存(延遲釋放),因為要將池中的所有內(nèi)容都執(zhí)行完才會釋放自動釋放池,所以占用內(nèi)存比較大的東西還是使用release為宜
創(chuàng)建自己的自動釋放池

Cocoa希望代碼在自動釋放池中執(zhí)行,否則發(fā)送了autorelease消息的對象便不能收到release消息,從而導(dǎo)致內(nèi)存泄漏.UIKit庫會在自動釋放池中處理每一個事件循環(huán),所以我們并不需要顯式地創(chuàng)建自動釋放池,不過如下三種情況下,我們?nèi)匀恍枰獎?chuàng)建自己的自動釋放池

  1. 如果不是基于UI庫(UI framework)進行開發(fā),而是采用命令行工具(command-line tool)進行開發(fā)
  2. 在使用循環(huán)時,尤其是循環(huán)次數(shù)多,且非常占用內(nèi)存的情況下,可以在循環(huán)內(nèi)部創(chuàng)建自己的自動釋放池,在每次循環(huán)時都將臨時變量銷毀,以免大量臨時變量堆積,導(dǎo)致內(nèi)存短時占用過高
  3. 如果大量產(chǎn)生第二線程,線程一被執(zhí)行就必須創(chuàng)建自己的自動釋放池,否則程序會內(nèi)存泄漏(這里未來需要進行詳細補充)
for (int i = 0; i < 999999; i++)
{
    @autoreleasepool
    {
        Person *p = [[[Person alloc] init] autorelease];
        // do something...
    }
}
類工廠方法內(nèi)存管理

在開發(fā)中,我們經(jīng)常使用Foundation框架中的類,在調(diào)用其類工廠方法創(chuàng)建一個對象時,因為并不是使用alloc,new,copy或者mutableCopy方法創(chuàng)建的,所以并不需要我們自己在給該對象發(fā)送release或者autorelease消息,這是因為類工廠方法內(nèi)部都已經(jīng)在返回對象前進行過延遲釋放

我們在自己書寫類工廠方法時,也應(yīng)該與系統(tǒng)處理方式相同,快速返回一個autorelease對象的方式具體如下

+ (instancetype)person
{
    // 使用self而不是使用Person是因為這樣可以在子類調(diào)用該方法時會返回子類的對象
    return [[[self alloc] init] autorelease];
}

快速返回一個帶有參數(shù)的autorelease對象的方式具體如下

+ (instancetype)personWithName:(NSString *)name
{
    Person *person = [[[self alloc] init] autorelease];
    person.name = name;
    return person;
}

注: Foundation框架中的類,所有通過類工廠方法創(chuàng)建的對象都是autorelease的,所以不用自己進行釋放

集合中對象的內(nèi)存管理

集合在iOS中包括NSArray、NSDictionary充易、NSSet,接下來以數(shù)組為例講解一下集合中的內(nèi)存管理規(guī)則

  • 將一個對象添加到一個數(shù)組中,數(shù)組會對該對象進行一次retain操作
  • 當(dāng)數(shù)組釋放后,會給數(shù)組中每個對象發(fā)送一條release消息
  • 當(dāng)數(shù)組移除一個對象后,會給這個對象發(fā)送一條release消息
NSMutableArray *array = [NSMutableArray array];
for (NSUInteger i = 0; i < 10; i++) 
{
    NSNumber *allocedNumber = [[NSNumber alloc] initWithInteger:i];
    // allocedNumber對象被加入數(shù)組,數(shù)組會對該對象進行一次retain操作
    [array addObject:allocedNumber];
    [allocedNumber release];
}

// 當(dāng)array對象被釋放,系統(tǒng)會給數(shù)組中的每個allocedNumber對象發(fā)送一條release消息,保證數(shù)組中的每個元素都能正常釋放

內(nèi)存管理中需要注意的兩個使用場景

  • 當(dāng)一個指針?biāo)赶虻膶ο鬄閿?shù)組中的某一個元素時,如果數(shù)組將該元素移除或者數(shù)組對象自身被釋放,此時該指針指向的對象已經(jīng)被釋放了
heisenObject = [array objectAtIndex:n];
[array removeObjectAtIndex:n];
// heisenObject被從數(shù)組中移除,會收到一條release消息,此時heisenObject所指向的對象已經(jīng)被釋放了

// 在實際開發(fā)中,我們并不希望這樣的情況發(fā)生,所以需要使用retain擁有該對象的所有權(quán)
heisenObject = [[array objectAtIndex:n] retain];
[array removeObjectAtIndex:n];
// do something...
[heisenObject release];
  • 當(dāng)一個指針?biāo)赶虻膶ο笫峭ㄟ^另一個對象獲得的,如果另一個對象自身被釋放,此時該指針指向的對象已經(jīng)被釋放了
id parent = <#create a parent object#>;
heisenObject = [parent child];
[parent release];
// heisenObject所指向的對象是通過另一個對象(例子中的parent對象)獲得的,另一個對象是該對象的唯一擁有者,如果另一個對象被釋放了,此時heisenObject所指向的對象已經(jīng)被釋放了

// 在實際開發(fā)中,我們并不希望這樣的情況發(fā)生,所以需要使用retain擁有該對象的所有權(quán)
id parent = <#create a parent object#>;
heisenObject = [[parent child] retain];
[parent release];
// do something...
[heisenObject release];

iOS內(nèi)存管理之ARC

概述

ARC(Automatic Reference Counting,自動引用計數(shù)),是iOS4引入的一項新技術(shù)(從iOS5開始支持弱引用),其使用與MRR&MRC相同的內(nèi)存管理規(guī)則來管理內(nèi)存,不過編譯器會在編譯階段自動地在適當(dāng)?shù)奈恢貌迦雛etain梗脾、release和autorelease等內(nèi)存管理代碼來管理內(nèi)存(屬于編譯器特性,不是運行時特性),不再需要程序人員手動管理.官方強烈建議使用ARC方式來管理內(nèi)存

注: OC中的ARC和Java中的垃圾回收機制不一樣,Java中的垃圾回收是系統(tǒng)做的,而OC中的ARC是編譯器做的

展示ARC與MRC使用區(qū)別的小示例
// MRC
@interface Person : NSObject

@property (retain) NSNumber *number;

@end

@implementation Person

- (void)dealloc
{
    NSLog(@"Person dealloc");
    
    [_number release];
    
    [super dealloc];
}

@end

Person *person = [[Person alloc] init];
NSNumber *number = [[NSNumber alloc] initWithInt:2];
person.number = number;

[number release];
[person release];
// perosn和number正常被釋放
// ARC
@interface Person : NSObject

@property (strong) NSNumber *number;

@end

@implementation Person

- (void)dealloc
{
    NSLog(@"Person dealloc");
}

@end

Person *person = [[Person alloc] init];
NSNumber *number = [[NSNumber alloc] initWithInt:2];
person.number = number;

// perosn和number出了作用域正常被釋放
ARC管理內(nèi)存的優(yōu)勢

消除了手動管理內(nèi)存的煩惱,不再需要手動調(diào)用retain、release和autorelease等方法來管理內(nèi)存(實際上在ARC模式下已經(jīng)不能調(diào)用出來該方法了),編譯器在編譯階段會自動地在適當(dāng)?shù)奈恢貌迦脒@些代碼,這樣可以極大概率地避免出現(xiàn)內(nèi)存問題.同時編譯器也會在一些地方執(zhí)行某些優(yōu)化,讓代碼性能更高,官方提供了一個能夠體現(xiàn)出來這種優(yōu)勢的插圖,如下

ARC.png
ARC與MRC的混合開發(fā)
  • 如果想在ARC項目中使用MRC文件,可以在Build Phases中的Compile Sources中對應(yīng)文件加入編譯標(biāo)記-fno-objc-arc
  • 如果想在MRC項目中使用ARC文件,可以在Build Phases中的Compile Sources中對應(yīng)文件加入編譯標(biāo)記-fobjc-arc

ARC引入的新規(guī)則

為了使ARC能夠正常工作,在ARC中引入了一些區(qū)別于當(dāng)前編譯模式的新的規(guī)則,如果你違反了這些規(guī)則,在編譯階段編譯器會給出一個警告

  • 不能實現(xiàn)或者調(diào)用retain盹靴、release炸茧、autorelease和retainCount方法,甚至不能使用@selector(retain)瑞妇、@selector(release)等方式調(diào)用
  • 不能調(diào)用dealloc方法
    • 可以實現(xiàn)dealloc方法,用于釋放除了實例變量以外的其他資源
    • 不需要在這里釋放實例變量(實際上也不能在這里給實例變量發(fā)送release消息)
    • 可以在這里調(diào)用[systemClassInstance setDelegate:nil],以便處理不是用ARC編譯的systemClass(在MRC下delegate使用assign修飾,如果自身被釋放,delegate會變成野指針,所以需要在dealloc中將其置空;在ARC下delegate使用weak修飾,如果自身被釋放,delegate會自動置空)
    • 不需要調(diào)用[super dealloc],編譯器會自動調(diào)用
  • 不能使用NSAutoreleasePool來創(chuàng)建自動釋放池,而是需要使用@autoreleasepool來代替
  • 為了與MRC之間進行互相操作,ARC中不允許給存取器命名為以new開頭(即不能聲明以new開頭的屬性),除非為該屬性定義一個新的getter名稱
// 錯誤
@property NSString *newTitle;
 
// 正確
@property (getter=theNewTitle) NSString *newTitle;

ARC引入的新特性

兩個屬性修飾符: strong和weak

在ARC中新增了兩個屬性修飾符: strong和weak,其中strong是默認修飾符,下面介紹一下這兩個屬性修飾符與retain和assign的區(qū)別

// 下面這句對于strong的示例,與此同義: @property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;

// 下面這句對于weak的示例,與此相似: @property(assign) MyClass *myObject;
// 使用assign修飾的指針?biāo)赶虻膶ο笕绻会尫?該指針會變成野指針;使用weak修飾的指針?biāo)赶虻膶ο笕绻会尫?該指針會變成空指針
@property(weak) MyClass *myObject;

針對于ARC中屬性修飾符的使用,要進行如下變化

  • strong用于OC對象,相當(dāng)于MRC中的retain
  • weak用于OC對象,相當(dāng)于MRC中的assign
  • assign用于基本數(shù)據(jù)類型,相當(dāng)于MRC中的assign

注: 其實就是將MRC中的assign分成了兩個部分,分別用于修飾OC對象與基本數(shù)據(jù)類型

四個變量修飾符

在ARC中新增了四個變量修飾符: 雙下劃線strong、雙下劃線weak梭冠、雙下劃線unsafe_unretained和雙下劃線autoreleasing,其中雙下劃線strong是默認修飾符,下面介紹一下這四個變量修飾符

  • 雙下劃線strong: 強引用,只要有強指針指向該變量,該變量便會一直存在
  • 雙下劃線weak: 弱引用,只要沒有強指針指向該變量,該變量便會被置空(即設(shè)置為nil)
  • 雙下劃線unsafe_unretained: 不安全的弱引用,只要沒有強指針指向該變量,該變量不會被置空(即設(shè)置為nil),而會變成野指針
  • 雙下劃線autoreleasing: 用于標(biāo)示自動釋放的變量

官方提醒,在為變量添加修飾符時,最正確的方式如下

// 規(guī)則
ClassName * qualifier variableName;

// 正確示例
MyClass * __weak myWeakReference;
MyClass * __unsafe_unretained myUnsafeReference;

// 錯誤示例(雖然錯誤,但是編譯器會默認為正確,官方說法為"forgiven")
__weak MyClass * myWeakReference;
__unsafe_unretained MyClass * myUnsafeReference;

注: 在直接使用__weak修飾變量指向一個剛創(chuàng)建的對象時,需要注意對象剛剛創(chuàng)建出來就會釋放的情況

NSString * __weak string = [[NSString alloc] initWithFormat:@"loly"];
// 因為沒有強指針指向該對象,該對象會立即被釋放

ARC中多對象內(nèi)存管理

概述

ARC中判斷對象是否應(yīng)該被釋放,不再觀察引用計數(shù)器是否為0,思想應(yīng)該轉(zhuǎn)變?yōu)槭欠裼袕娭羔樦赶蛟搶ο?只要有一個強指針指向該對象,該對象就會一直保持在內(nèi)存中不會被釋放

  • 當(dāng)A對象想要擁有B對象的所有權(quán)時,直接使用一個強指針指向它即可
  • 當(dāng)A對象想要放棄對B對象的所有權(quán)時,什么都不需要做,編譯器會自動處理

在涉及到多對象內(nèi)存管理時,在MRC中使用的存取器方法也需要進行一些調(diào)整

存取器方法
  • getter方法規(guī)范: 直接返回實例變量
// 針對基本數(shù)據(jù)類型
- (int)age
{
    return _age;
}

// 針對OC對象
- (Room *)room
{
    return _room;
}
  • setter方法規(guī)范:
// 針對基本數(shù)據(jù)類型,直接將新值賦值給實例變量
- (void)setAge:(int)age
{
    _age = age;
}

// 針對OC對象,直接將新值賦值給實例變量
- (void)setRoom:(Room *)room
{
    _room = room;
}

循環(huán)引用

因為ARC使用與MRC相同的引用計數(shù)規(guī)則,所以同樣也會出現(xiàn)循環(huán)引用問題

在MRC中的解決辦法是讓parent持有children的"強"引用,而children持有parent的"弱"引用,即將A retain B, B retain A中一方的retain改為assign即可

在ARC中的解決辦法與在MRC中原理相同,即將A strong B, B strong A中一方的strong改為weak即可

@interface Person : NSObject
@property (nonatomic, strong) Car *car;
@end

@interface Car : NSObject
@property (nonatomic, weak) Person *person;
@end

注: 當(dāng)使用Block時,會出現(xiàn)不易察覺的循環(huán)引用現(xiàn)象,關(guān)于這點我在另一篇文章中進行過介紹,這里不再贅述

ARC中使用新的方式創(chuàng)建自動釋放池

關(guān)于自動釋放池在上文已經(jīng)詳細介紹過,這里不再贅述

// MRC
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// do something...
[pool release];

// ARC
@autoreleasepool
{
    // do something...
}

棧變量在ARC中默認初始化為nil

在ARC中,棧變量會被初始化為nil,即使不進行賦值,程序也不會造成崩潰,示例如下

- (void)myMethod 
{
    NSString *name;
    NSLog(@"name: %@", name);
    // 打印結(jié)果為nil,不會崩潰
}

iOS內(nèi)存管理之Copy

概述

copy(復(fù)制辕狰、拷貝)是產(chǎn)生一個副本對象的過程,只要是通過拷貝產(chǎn)生的副本對象,副本對象中的內(nèi)容與源對象中的內(nèi)容就完全一致,下面介紹幾個copy相關(guān)的知識點

copy的特點
  • 修改源對象的屬性和行為,不會影響副本對象
  • 修改副本對象的屬性和行為,不會影響源對象
copy與mutableCopy
  • 使用copy產(chǎn)生的副本對象是不可變的(如NSString,NSArray)
  • 使用mutableCopy產(chǎn)生的副本對象是可變的(如NSMutableString,NSMutableArray)
通過拷貝是否會產(chǎn)生新的對象

通過拷貝是否會產(chǎn)生新對象,就要看源對象與副本對象是否滿足拷貝的特點

  • 可變對象通過mutableCopy,會生成新的對象
  • 可變對象通過copy,會生成新的對象
  • 不可變對象通過mutableCopy,會生成新的對象
  • 不可變對象通過copy,不會生成新的對象(因為源對象與副本對象都是不可變的,已經(jīng)滿足拷貝的特點)
深拷貝(內(nèi)容拷貝)與淺拷貝(指針拷貝)
  • 深拷貝: 如果通過拷貝生成了新對象,就稱為深拷貝(內(nèi)容拷貝)
  • 淺拷貝: 如果通過拷貝沒生成新對象,就稱為淺拷貝(指針拷貝)

注1: 通過深拷貝,源對象的引用計數(shù)器不變,副本對象的引用計數(shù)器為1,所以需要對副本對象進行一次release操作

注2: 通過淺拷貝,源對象的引用計數(shù)器+1,所以需要對源對象再進行一次release操作

copy與retain作為屬性修飾符的不同

  • 通過copy作為屬性修飾符來修飾對象的屬性,是對傳入變量進行了一次copy操作,在外界變量進行修改之后,對象的屬性不會隨之發(fā)生變化
  • 通過retain作為屬性修飾符來修飾對象的屬性,是對傳入變量進行了一次retain操作,在外界變量進行修改之后,對象的屬性會隨之發(fā)生了變化

注: 字符串的屬性都應(yīng)該使用copy修飾符進行修飾

示例
@interface Person : NSObject

@property (nonatomic, copy) NSString *theCopyName;
@property (nonatomic, strong) NSString *theStrongName;

@end

Person *p = [[Person alloc] init];
NSMutableString *mStr = [NSMutableString stringWithString:@"loly"];
p.theStrongName = mStr;
p.theCopyName = mStr;
[mStr appendString:@"y"];
NSLog(@"theStrongName = %@, theCopyName = %@", p.theStrongName, p.theCopyName);
// 打印結(jié)果: theStrongName = loly, theCopyName = lol

自定義類實現(xiàn)copy與mutableCopy功能

概述

一個類的對象想擁有copy與mutableCopy功能,都需要擁有一個前提

  • 使用copy的前提: 對應(yīng)類需要遵守NSCopying協(xié)議,并實現(xiàn)copyWithZone:方法
  • 使用mutableCopy的前提: 對應(yīng)類需要遵守NSMutableCopying協(xié)議,并實現(xiàn)mutableCopyWithZone:方法

注: OC中大部分類已經(jīng)遵守了NSCopying,NSMutableCopying協(xié)議

下面我們將通過兩個示例來介紹一下如何讓自定義類擁有copy與mutableCopy功能

實現(xiàn)Person類的copy與mutableCopy功能
@interface Person : NSObject <NSCopying, NSMutableCopying>

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

@end

@implementation Person

- (id)copyWithZone:(NSZone *)zone
{
    // 1.創(chuàng)建一個新對象
    Person *p = [[[self class] allocWithZone:zone] init];
    // 2.設(shè)置當(dāng)前對象的內(nèi)容給新的對象
    p.name = _name;
    p.age = _age;
    // 3.返回新的對象
    return p;
}

- (id)mutableCopyWithZone:(NSZone *)zone
{
    Person *p = [[[self class] allocWithZone:zone] init];
    p.name = _name;
    p.age = _age;
    return p;
}

@end
實現(xiàn)Student類(繼承自Person類)的copy與mutableCopy功能
@interface Student : Person

@property (nonatomic, assign) float height;

@end

@implementation Student

- (id)copyWithZone:(NSZone *)zone
{
    // 1.創(chuàng)建一個新對象
    id obj = [super copyWithZone:zone];
    // 2.設(shè)置當(dāng)前對象的內(nèi)容給新的對象
    [obj setHeight:_height];
    // 3.返回新的對象
    return obj;
}

- (id)mutableCopyWithZone:(NSZone *)zone
{
    id obj = [super mutableCopyWithZone:zone];
    [obj setHeight:_height];
    return obj;
}

@end

參考文獻

  1. Advanced Memory Management Programming Guide
  2. Transitioning to ARC Release Notes
  3. 互聯(lián)網(wǎng)相關(guān)技術(shù)博客及視頻
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市控漠,隨后出現(xiàn)的幾起案子蔓倍,更是在濱河造成了極大的恐慌,老刑警劉巖盐捷,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偶翅,死亡現(xiàn)場離奇詭異,居然都是意外死亡毙驯,警方通過查閱死者的電腦和手機倒堕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來爆价,“玉大人垦巴,你說我怎么就攤上這事∶危” “怎么了骤宣?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長序愚。 經(jīng)常有香客問我憔披,道長,這世上最難降的妖魔是什么爸吮? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任芬膝,我火速辦了婚禮,結(jié)果婚禮上形娇,老公的妹妹穿的比我還像新娘锰霜。我一直安慰自己,他們只是感情好桐早,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布癣缅。 她就那樣靜靜地躺著,像睡著了一般哄酝。 火紅的嫁衣襯著肌膚如雪友存。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天陶衅,我揣著相機與錄音屡立,去河邊找鬼。 笑死搀军,一個胖子當(dāng)著我的面吹牛侠驯,可吹牛的內(nèi)容都是我干的抡秆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼吟策,長吁一口氣:“原來是場噩夢啊……” “哼儒士!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起檩坚,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤着撩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后匾委,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拖叙,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年赂乐,在試婚紗的時候發(fā)現(xiàn)自己被綠了薯鳍。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡挨措,死狀恐怖挖滤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情浅役,我是刑警寧澤斩松,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站觉既,受9級特大地震影響惧盹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瞪讼,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一钧椰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧符欠,春花似錦嫡霞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悬赏。三九已至狡汉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間闽颇,已是汗流浹背盾戴。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留兵多,地道東北人尖啡。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓橄仆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親衅斩。 傳聞我的和親對象是個殘疾皇子盆顾,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容