本文來自內(nèi)存管理文檔的整理
在Objective-C中內(nèi)存管理是基于引用計(jì)數(shù)的,所謂的引用計(jì)數(shù)就是每個(gè)對象都會有一個(gè)引用計(jì)數(shù)記錄這個(gè)對象跟誰有聯(lián)系,當(dāng)這個(gè)引用計(jì)數(shù)為0的時(shí)候,對象就會釋放.
在Cocoa框架下內(nèi)存管理的策略:
1.你自己通過new alloc,copy,mutableCopy創(chuàng)建的對象,則自己擁有這個(gè)對象的所有權(quán).
2.你可以使用retain獲取對象的所有權(quán),即對其引用計(jì)數(shù)+1.通過這個(gè)方法延長對象的聲明周期,防止其提前釋放掉
3.當(dāng)你不需要這個(gè)對象的時(shí)候,記得使用release或autorelease放棄你對對象的所有權(quán)
4.你不能釋放不是你擁有的對象的所有權(quán).
ps: 使用非 new ,alloc,copy,mutableCopy創(chuàng)建的對象,如[NSString stringWithFormat:@""]則得到的對象會自動的被標(biāo)記為autorelease.
MRR下實(shí)現(xiàn)set get 方法:
//set 方法
- (void)setCount:(NSNumber *)newCount {
[newCount retain];
[_count release];
// Make the new assignment.
_count = newCount;
}
//get方法
- (NSNumber *)count {
return _count;
}
5.weak引用是一種非擁有的狀態(tài),使用weak修飾的變量并不會retain weak指向的對象.日常開發(fā)中有一下幾種情況需要特殊注意:
delegate,Block,還有Timer. delegate和Block老生常談這就細(xì)說了.
在NSTimer的文檔中我們可以看到關(guān)于target的注解:
The object to which to send the message specified by?aSelector?when the timer fires. The timer maintains a strong reference to?target?until it (the timer) is invalidated.
timer 會對target保持強(qiáng)引用直到timer調(diào)用invalidated方法.若是target也持有timer那么循環(huán)引用就會產(chǎn)生了
而且因?yàn)閠imer是一個(gè)循環(huán)的調(diào)用,會被加入到Runloop中,如果不調(diào)用invalidated,則timer會被Runloop一直強(qiáng)引用,直到Runloop退出.若是主線程的Runloop則會一直等到程序結(jié)束,timer都不會被釋放
平常開發(fā)中記得一定要調(diào)用invalidated方法一般不會出現(xiàn)問題,但是某些特定的情況還是會出現(xiàn)沒有合適的時(shí)機(jī)去調(diào)用invalidated,所以要避免循環(huán)引用.
目前有幾種比較合理的解決方案:
第一中,寫一個(gè)Timer的分類,讓timer的target引用timer的類指針,這樣避免產(chǎn)生循環(huán)引用
typedefvoid(^QSExecuteTimerBlock) (NSTimer*timer);
@interfaceNSTimer(QSTool)
+ (NSTimer*)qs_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval executeBlock:(QSExecuteTimerBlock)block repeats:(BOOL)repeats;
@end
@implementationNSTimer(QSTool)
+ (NSTimer*)qs_scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval executeBlock:(QSExecuteTimerBlock)block repeats:(BOOL)repeats{
NSTimer*timer = [selfscheduledTimerWithTimeInterval:timeInterval target:selfselector:@selector(qs_executeTimer:) userInfo:[blockcopy] repeats:repeats];
return timer;
}+ (void)qs_executeTimer:(NSTimer*)timer{?
?QSExecuteTimerBlock block = timer.userInfo;
if(block) { block(timer); }
}
@end
這個(gè)方案比較簡潔,好用比較推薦使用,另外這段代碼要感謝@南華coder.
第二種方法就是通過一個(gè)代理,讓代理去弱引用真的target,而timer去持有代理,這樣就避免了循環(huán)引用了.下面是代理的實(shí)現(xiàn):
@implementation BTWeakTimerTarget
{
?__weak target;?
?SEL selector;
}
[...]
- (void)timerDidFire:(NSTimer *)timer{?
?if(target) {
?[target performSelector:selector withObject:timer];?
?} else {
?[timer invalidate];?
?}}
@end
然后,你使用的時(shí)候
BTWeakTimerTarget *target = [[BTWeakTimerTarget alloc] initWithTarget:self selector:@selector(tick)];
timer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:target selector:@selector(timerDidFire:) ...];
ps:在Cocoa框架內(nèi)Notification 和 KVO 都是weak 引用
NSNotificationCenter 會若引用被監(jiān)聽的對象
Neither the object receiving this message, nor?observer, are retained
這段話是KVO的官方文檔的解釋
KVO 中無論是觀察者還是被觀察者,兩者都是若引用
6.不要在dealloc內(nèi)部做一些有關(guān)資源的操作
7.集合類型當(dāng)你把一個(gè)對象加入其中,會被自動retain,當(dāng)你把對象從集合中移除則會被自動realease
8.不要依賴引用計(jì)數(shù)去判斷內(nèi)存是否泄漏這些問題,因?yàn)橐糜?jì)數(shù)并不準(zhǔn)確,有可能有錯(cuò)誤.
接下來就會介紹比較復(fù)雜的Autorelease
在NSObjec.mm文件可以看到Autorelease的實(shí)現(xiàn),代碼太多不放上來,我們看一下注釋
/*
Autorelease pool implementation
? A thread's autorelease pool is a stack of pointers.
? Each pointer is either an object to release, or POOL_BOUNDARY which is
? ? an autorelease pool boundary.
? A pool token is a pointer to the POOL_BOUNDARY for that pool. When
? ? the pool is popped, every object hotter than the sentinel is released.
? The stack is divided into a doubly-linked list of pages. Pages are added
? ? and deleted as necessary.
? Thread-local storage points to the hot page, where newly autoreleased
? ? objects are stored.
*/
簡單翻譯一下就是:
每個(gè)線程的?autoreleasepool 是一個(gè)儲存指針的棧
每個(gè)指針都代表一個(gè)要釋放的對象或者是?autoreleasepool的邊界
一個(gè) pool token 是一個(gè)指向POOL_BOUNDARY的指針,當(dāng)pool開始pop的時(shí)候,每個(gè)哨兵對象后面的對象都會釋放
這個(gè)棧是一個(gè)以page為節(jié)點(diǎn)的雙向鏈表
Thread-local storage(線程局部存儲)指向 hot page 怎抛,即最新添加的 autoreleased 對象所在的那個(gè) page 。
基本可以得到一些信息,Autorelease pool 其實(shí)就是一個(gè)棧,內(nèi)部存儲自動釋放的對象,當(dāng)達(dá)到一個(gè)臨界點(diǎn)的時(shí)候就會釋放.這個(gè)臨界點(diǎn)我們可以在官方文檔中看到
At the end of the autorelease pool block, objects that received an?autoreleasemessage within the block are sent a?release?message—an object receives a?release?message for each time it was sent an?autorelease?message within the block.
意思就是當(dāng)自動釋放的對象在超出自動釋放池的作用域后開始準(zhǔn)備釋放
那么什么時(shí)候需要自己創(chuàng)建自動釋放池呢
1.當(dāng)你寫一個(gè)不是基于UI Framework的程序,比如命令行工具
2.當(dāng)你寫一個(gè)循環(huán)創(chuàng)建了大量的臨時(shí)變量
3.當(dāng)你創(chuàng)建了子線程的時(shí)候
寫到這,可能大家會說平常開發(fā),出現(xiàn)子線程并沒有創(chuàng)建Autorelease Pool,也沒有內(nèi)存泄漏.
這個(gè)問題我也是很疑惑,不過看了官方文檔的描述和@Joy___的文章之后,大概知道了原因了,
官方文檔中有描述到,每個(gè)線程都會有自己的Autorelease Pool棧,在子線程你創(chuàng)建了 Pool 的話楞遏,產(chǎn)生的 Autorelease 對象就會交給 pool 去管理省店。如果你沒有創(chuàng)建 Pool 包晰,但是產(chǎn)生了 Autorelease 對象趟畏,就會調(diào)用 autoreleaseNoPage 方法。在這個(gè)方法中闰靴,會自動幫你創(chuàng)建一個(gè) hotpage(hotPage 可以理解為當(dāng)前正在使用的 AutoreleasePoolPage牡肉,如果你還是不理解捧灰,可以先看看 Autoreleasepool 的源代碼,再來看這個(gè)問題 )统锤,并調(diào)用page->add(obj)將對象添加到 AutoreleasePoolPage 的棧中毛俏,也就是說你不進(jìn)行手動的內(nèi)存管理,也不會內(nèi)存泄漏啦饲窿!StackOverFlow 的作者也說道煌寇,這個(gè)是 OS X 10.9+和 iOS 7+ 才加入的特性。并且蘋果沒有對應(yīng)的官方文檔闡述此事逾雄,但是你可以通過源碼了解阀溶。
以上就是對Object-c的內(nèi)存理解