聽說這本書很好,所以在項目不怎么忙的時候就讀了讀。總結(jié)了點筆記。
手動內(nèi)存管理MRC
- 內(nèi)存管理的思想
思想一:自己生成的對象,自己持有。
思想二:非自己生成的對象,自己也能持有。
思想三:不再需要自己持有的對象時釋放對象。
思想四:非自己持有的對象無法釋放。
從上面的思想來看,我們對對象的操作可以分為三種:生成,持有,釋放,再加上廢棄,一共有四種。它們所對應(yīng)的Objective-C的方法和引用計數(shù)的變化是:
思想一:自己生成的對象,自己持有
alloc
new
copy
mutableCopy
如 id obj = [[NSObject alloc] init];//持有新生成的對象
思想二:非自己生成的對象,自己也能持有
id obj = [NSMutableArray array];//非自己生成并持有的對象
[obj retain];//持有新生成的對象
來看看[NSMutableArray array]是怎么個實現(xiàn)法,和自己生成的對象有什么區(qū)別:
- (id)object
{
id obj = [[NSObject alloc] init];//持有新生成的對象
[obj autorelease];//自動釋放
return obj;
}
通過autorelease方法,使對象的持有權(quán)轉(zhuǎn)移給了自動釋放池。所以實現(xiàn)了:調(diào)用方拿到了對象,但這個對象還不被調(diào)用方所持有.
思想三:不再需要自己持有的對象時釋放對象
id obj = [[NSObject alloc] init];//持有新生成的對象
[obj release];//事情做完了,釋放該對象
或者是
id obj = [NSMutableArray array];//非自己生成并持有的對象
[obj retain];//持有新生成的對象
[obj release];//事情做完了,釋放該對象
思想四:無法釋放非自己持有的對象
情況一:多次釋放。
id obj = [[NSObject alloc] init];//持有新生成的對象
[obj release];//釋放該對象,不再持有了
[obj release];//釋放已經(jīng)廢棄了的對象,崩潰
情況二:對象不被自己持有,就釋放。
id obj = [NSMutableArray array];//非自己生成并持有的對象
[obj release];//釋放了非自己持有的對象
實現(xiàn)原理
Objective-C對象中保存著引用計數(shù)這一整數(shù)值。
調(diào)用alloc或者retain方法后,引用計數(shù)+1。
調(diào)用release后,引用計數(shù)-1。
引用計數(shù)為0時,調(diào)用dealloc方法廢棄對象。
autorelease
autorelease的具體使用方法如下:
生成并持有NSAutoreleasePool對象。
調(diào)用已分配對象的autorelease方法。
廢棄NSAutoreleasePool對象。
所有調(diào)用過autorelease方法的對象,在廢棄NSAutoreleasePool對象時,都將調(diào)用release方法(引用計數(shù)-1)
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];//相當(dāng)于obj調(diào)用release方法
NSRunLoop在每次循環(huán)過程中,NSAutoreleasePool對象都會被生成或廢棄.
這樣會有個問題如果是生成大量的autorelease對象,只要不廢棄NSAutoreleasePool的話對象就不能釋放,所以會產(chǎn)生內(nèi)存不足的問題。由上圖NSRunLoop是在應(yīng)用程序主線程處理完了才會廢棄Pool。所以如果在for循環(huán)里創(chuàng)建好多的局部對象,他們得不到及時的釋放,就會使得程序因為內(nèi)存不足奔潰。
如
for (int i = 0; i < 10000; i++)
{
圖像文件讀入到data對象,
data生成UIimage對象,
改變對象的尺寸生成一個新的UIimage對象
}
解決辦法:
for (int i = 0; i < 10000; i++)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
圖像文件讀入到data對象,
data生成UIimage對象,
改變對象的尺寸生成一個新的UIimage對象
[pool drain];//相當(dāng)于obj調(diào)用release方法
}
每個循環(huán)都會創(chuàng)建個池子,池子也是循環(huán)完了就釋放。所以自動變量也都釋放了。自動變量都是加在離它最近的池子的。
蘋果的實現(xiàn)
class AutoreleasePoolPage
{
static inline void *push()
{
//生成或者持有 NSAutoreleasePool 類對象
}
static inline void pop(void *token)
{
//廢棄 NSAutoreleasePool 類對象
releaseAll();
}
static inline id autorelease(id obj)
{
//相當(dāng)于 NSAutoreleasePool 類的 addObject 類方法
AutoreleasePoolPage *page = 取得正在使用的 AutoreleasePoolPage 實例;
autoreleaesPoolPage->add(obj)
}
id *add(id obj)
{
//將對象追加到內(nèi)部數(shù)組中
}
void releaseAll()
{
//調(diào)用內(nèi)部數(shù)組中對象的 release 方法
}
};
//壓棧
void *objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
//出棧
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
id *objc_autorelease(id obj)
{
return AutoreleasePoolPage::autorelease(obj);
}
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 等同于 objc_autoreleasePoolPush
id obj = [[NSObject alloc] init];
[obj autorelease];
// 等同于 objc_autorelease(obj)
[pool drain];
// 等同于 objc_autoreleasePoolPop(pool)
想要了解更多autoreleasepool原理看這個http://blog.leichunfeng.com/blog/2015/05/31/objective-c-autorelease-pool-implementation-principle/
ARC下的內(nèi)存管理
在ARC機制下,編譯器就可以自動進行內(nèi)存管理,減少了開發(fā)的工作量。但我們有時仍需要四種所有權(quán)修飾符來配合ARC來進行內(nèi)存管理。
四種所有權(quán)修飾符
ARC提供四種修飾符,分別是strong, weak, autoreleasing, unsafe_unretained
__strong:強引用,持有所指向?qū)ο蟮乃袡?quán),無修飾符情況下的默認值。如需強制釋放,可置nil。
NSObject *obj = [[NSObject alloc]init];
他們是等價的
NSObject *__strong obj = [[NSObject alloc]init];
obj = nil;
__weak:弱引用,不持有所指向?qū)ο蟮乃袡?quán),引用指向的對象內(nèi)存被回收之后,引用本身會置nil,避免野指針。
比如避免循環(huán)引用的弱引用聲明:
__weak __typeof(self) weakSelf = self;
_autoreleasing:將對象賦值給附有 _ autoreleasing 修飾符的變量等同于ARC 無效時調(diào)用對象的autorelease方法
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
unsafe_unretained:相當(dāng)于assign。直接賦值。引用計數(shù)不變。他會發(fā)生野指針現(xiàn)象。所以不安全。不像weak,當(dāng)指向的對象為空的時候,將指針置為nil。
屬性的內(nèi)存管理
ObjC2.0引入了@property,提供成員變量訪問方法、權(quán)限、環(huán)境、內(nèi)存管理類型的聲明,下面主要說明ARC中屬性的內(nèi)存管理
屬性的參數(shù)分為三類,基本數(shù)據(jù)類型默認為(atomic,readwrite,assign),對象類型默認為(atomic,readwrite,strong),其中第三個參數(shù)就是該屬性的內(nèi)存管理方式修飾,修飾詞可以是以下之一:
assign:直接賦值
assign一般用來修飾基本數(shù)據(jù)類型
@property (nonatomic, assign) NSInteger count;
當(dāng)然也可以修飾ObjC對象,但是不推薦,因為被assign修飾的對象釋放后,指針還是指向釋放前的內(nèi)存,在后續(xù)操作中可能會導(dǎo)致內(nèi)存問題引發(fā)崩潰。
retain
retain和strong一樣,都用來修飾ObjC對象
使用set方法賦值時,實質(zhì)上是會先保留新值,再釋放舊值,再設(shè)置新值,避免新舊值一樣時導(dǎo)致對象被釋放的的問題
MRC寫法如下
- (void)setCount:(NSObject *)count {
[count retain];
[_count release];
_count = count;
}
ARC對應(yīng)寫法
- (void)setCount:(NSObject *)count {
_count = count;
}
copy
一般用來修飾String、Dict、Array等需要保護其封裝性的對象,尤其是在其內(nèi)容可變的情況下,因此會拷貝(深拷貝)一份內(nèi)容給屬性使用,避免可能造成的對源內(nèi)容進行改動。
使用set方法賦值時,實質(zhì)上是會先拷貝新值,再釋放舊值,再設(shè)置新值。
實際上,遵守NSCopying的對象都可以使用copy,當(dāng)然,如果你確定是要共用同一份可變內(nèi)容,你也可以使用strong或retain。
weak:ARC新引入修飾詞,可代替assign,比assign多增加一個特性(置nil)
weak和strong一樣用來修飾ObjC對象。
使用set方法賦值時,實質(zhì)上不保留新值,也不釋放舊值,只設(shè)置新值。
@property (weak) id<MyDelegate> delegate;
strong:
ARC新引入修飾詞,可代替retain.ARC一般都寫strong。
block的內(nèi)存管理
OS中使用block必須自己管理內(nèi)存,錯誤的內(nèi)存管理將導(dǎo)致循環(huán)引用等內(nèi)存泄漏問題,這里主要說明在ARC下block聲明和使用的時候需要注意的兩點:
1)如果你使用@property去聲明一個block的時候,一般使用copy來進行修飾(當(dāng)然也可以不寫,編譯器自動進行copy操作),盡量不要使用retain。
@property (nonatomic, copy) void(^block)(NSData * data);
block會對內(nèi)部使用的對象進行強引用,因此在使用的時候應(yīng)該確定不會引起循環(huán)引用,當(dāng)然保險的做法就是添加弱引用標記。
__weak typeof(self) weakSelf = self;
循環(huán)中對象占用內(nèi)存大
這個問題常見于循環(huán)次數(shù)較大,循環(huán)體生成的對象占用內(nèi)存較大的情景。
for (int i = 0; i < 10000; i ++) {
Person * p = [[Person alloc]init];
}
該循環(huán)內(nèi)產(chǎn)生大量的臨時對象,直至循環(huán)結(jié)束才釋放,可能導(dǎo)致內(nèi)存泄漏,在循環(huán)中創(chuàng)建自己的autoReleasePool,及時釋放占用內(nèi)存大的臨時變量,減少內(nèi)存占用峰值。
for (int i = 0; i < 10000; i ++) {
@autoreleasepool {
Person * p = [[Person alloc]init];
}
}
特殊情況
假如有10000張圖片,每張2M左右,現(xiàn)在需要獲取所有圖片的尺寸,你會怎么做?
用imageNamed方法加載圖片默認會寫到緩存里,autoReleasePool也不能釋放緩存,對此問題需要另外的解決方法