iOS內(nèi)存管理
在Objective-C中有兩套內(nèi)存管理策略,MRC(Mannul Reference Counting)和ARC(Automatic Referenct Counting),但是兩套都是基于引用計數(shù)來進(jìn)行內(nèi)存管理的。
MRC遵循著誰創(chuàng)建誰釋放,一般會有如下情況,創(chuàng)建一個新對象的方法:new,alloc,copy,mutableCopy,此時的引用計數(shù)都是retain count都是為1。此時當(dāng)進(jìn)行了如下操作,新對象會獲取原來對象的所有權(quán),引用計數(shù)就會增加,id newObj = [oldObj retain], 所以,針對如上的操作都需要手動調(diào)用release。如下,[newObj release].
ARC是自iOS5之后推出的新的內(nèi)存管理策略,一種自動引用計數(shù)技術(shù)。其管理內(nèi)存的策略和MRC一致,僅僅只是編譯器在特定的位置幫我們加上release代碼。
Automatic Reference Counting (ARC) is a compiler feature that provides automatic memory management of Objective-C objects. Rather than having to think about retain and release operations, ARC allows you to concentrate on the interesting code.
本人也是從iOS7開始接觸iOS開發(fā)的。所以一直以來都是使用ARC內(nèi)存管理規(guī)則。
ARC works by adding code at compile time to ensure that objects live as long as necessary, but no longer.
ARC的解釋是在編譯時,編譯器會在對象的生命周期中添加必要的代碼(release代碼)。即,現(xiàn)在不需要我們手動的調(diào)用release
-
iOS 中可能的內(nèi)存泄漏
- 循環(huán)引用
只要是使用了的是引用計數(shù)策略進(jìn)行內(nèi)存管理的,就會有出現(xiàn)循環(huán)引用的可能性,導(dǎo)致內(nèi)存無法釋放掉而內(nèi)存泄漏。-
對象間的循環(huán)引用 ,使用弱引用來破解。如下,分別有objA,objB,objC,三個對象,屬性間相互引用,造成了循環(huán)引用。此時可以設(shè)置對象objC指向?qū)ο髈bjA的屬性為弱引用類型。
objA.property = objB; objB.property = objC; objC.property = objA; -
對象和Block之間的循環(huán)引用
self.someBlock = ^{ self.property = xxx; };如上出現(xiàn)了循環(huán)引用,此時編譯器可以檢測出,直接會報錯。但是當(dāng)有其他對象介入的時候時候,編譯器就不能在編譯期發(fā)現(xiàn)是否出現(xiàn)了循環(huán)引用。
someObj.someBlock = ^{ self.property = xxx;}; self.someObj = someObj;- 解決方案
使用
__weak修飾,弱化block中對象引用。如下__weak SomeObjectClass *weakSelf = self; someObj.someBlock = ^{ SomeObjectClass *strongSelf = weakSelf; if (strongSelf == nil) { // handle } [strongSelf sendMsg]; }; -
NSTimer
在OC中有很多的
Action/Target的模式,大多數(shù)的情況下都是弱引用了target,但是在NSTimer中并不是這樣的,timer對象強(qiáng)引用了target+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats解釋target : The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated.
-
- 循環(huán)引用
UITableView的優(yōu)化
UITableView對于iOS開發(fā)而言,不能再熟悉了,這也是iOS和Android流暢性最為直觀的地方。所以做好UITableView的優(yōu)化也是至關(guān)重要的。(,其實只要在開發(fā)中保持一個好習(xí)慣就行)
概念
UITableView最為關(guān)鍵的就是幾個代理方法,一個是返回cell的- tableView:cellForRowAtIndexPath:,一個是返回高度的- tableView:heightForRowAtIndexPath:。由于UITableView是繼承于UIScrollView,所以必須優(yōu)先計算出contentSize,所有會先調(diào)用- tableView:heightForRowAtIndexPath:進(jìn)行整體布局,之后調(diào)用返回cell的方法,進(jìn)行cell內(nèi)部的布局。
所以:
- 返回高度的方法
- tableView:heightForRowAtIndexPath:調(diào)用次數(shù)最多。 - 返回cell的方法
- tableView:cellForRowAtIndexPath:最耗時。
優(yōu)化:
- 在獲取到數(shù)據(jù)后,把cell的高度提前計算好,存儲在model(或者其他地方)中,在
- tableView:heightForRowAtIndexPath:回調(diào)中直接使用。 - 對cell中不要添加太多的subView。更多的subView需要更多的布局等其他方面的計算。
- 對于要求很高的tableView,cell不要使用xib,畢竟需要對xib進(jìn)行轉(zhuǎn)化。
- 可以用去繪畫一個cell,不用系統(tǒng)自帶的view.(對tableView滑動要求更高)
- 使用動態(tài)按需加載,即只有當(dāng)tableView快速滑動結(jié)束后再加載可視范圍內(nèi)的cell
tableView其他一些常用的正確的用法:
- 正確使用重用機(jī)制
- cell中盡量不要使用alpha值不為1的view,不然會造成圖層混合
- cell中不要大量設(shè)置圓角,避免出現(xiàn)離屏渲染。需要圓角可以自己繪制。
- cell中圖層盡量不設(shè)置陰影,必要時可以使用柵格化技術(shù),和自行繪制陰影路徑,防止由于陰影照成的離屏渲染。
- 在cell中最好不要動態(tài)的創(chuàng)建subview,最好在初始化的時候創(chuàng)建好subview,之后使用屬性
hide來控制顯示和隱藏。 - cell中的需要加載的東西來自于web,使用異步加載的方式。
具體的參照
圓角繪制: iOS - 圓角的設(shè)計策略 - 及繪畫出沒有離屏渲染的圓角
圖層調(diào)試: 記-UI性能調(diào)試及調(diào)優(yōu)
UITableView優(yōu)化:UITableView優(yōu)化技巧
UDP和TCP
TCP和UDP是計算機(jī)網(wǎng)絡(luò)中學(xué)習(xí)到的概念,它們兩者都屬于運(yùn)輸層。
TCP(Transmission Control Protocol)是一種面相連接,可靠的流協(xié)議,其中連接的意思指的是兩個應(yīng)用為了通信,在兩者之間建立的一種虛擬的線路??煽康牧鲄f(xié)議指的是不間斷數(shù)據(jù)結(jié)構(gòu),就比如管道中的水流,TCP提供了如重發(fā)機(jī)制,流量控制等一系列的可靠性保障。雖然有TCP有很多優(yōu)點,也有不足的地方,就是傳輸效率比較低。
UDP(User Datagram Protocol)是一種不具有可靠性的數(shù)據(jù)報協(xié)議,只負(fù)責(zé)發(fā)送信息,但是不確定對方是否收到。但是優(yōu)點就是效率非常高。
使用場景:場景都是基于其優(yōu)缺點。由于TCP傳輸可靠性很強(qiáng),所以在傳輸文件,收發(fā)郵件等。UDP效率高,可靠性比較低,一般用于網(wǎng)絡(luò)電話,視頻,語音等,必須是實時的,其中可能有一點點斷斷續(xù)續(xù),可能影響不會很大。
SDWebImage的原理
SDWebImage作為開源第三方框架中的姣姣者,經(jīng)常被我們的項目所用到,看似很復(fù)雜,其實實現(xiàn)的原理中并不算很復(fù)雜——總的來說
1. 入口setImageWithURL:placeholderImage:options:開始
2. 先從內(nèi)存緩存中查找,是否存在圖片,存在,顯示圖片。
3. 不存在,開啟一個新線程從磁盤緩存中查找,查看是否存在圖片,存在,回調(diào)到主線程,顯示圖片。
4. 不存在,開始一個新的線程下載圖片。下載成功,對圖片進(jìn)行解碼,解碼成功后,回調(diào)到主線程,顯示圖片。并且緩存圖片到內(nèi)存緩存和磁盤緩存中。
具體的詳細(xì)步驟:如下
- UIImageView+WebCache:
setImageWithURL:placeholderImage:options:先顯示 placeholderImage ,同時由SDWebImageManager 根據(jù) URL 來在本地查找圖片.。- SDWebImageManager:
downloadWithURL:delegate:options:userInfo:SDWebImageManager是將UIImageView+WebCache同SDImageCache鏈接起來的類, SDImageCache:queryDiskCacheForKey:delegate:userInfo:用來從緩存根據(jù)CacheKey查找圖片是否已經(jīng)在緩存中
- 如果內(nèi)存中已經(jīng)有圖片緩存, SDWebImageManager會回調(diào)SDImageCacheDelegate :
imageCache:didFindImage:forKey:userInfo:
- 而 UIImageView+WebCache 則回調(diào)SDWebImageManagerDelegate:
webImageManager:didFinishWithImage:來顯示圖片。
- 如果內(nèi)存中沒有圖片緩存,那么生成 NSInvocationOperation 添加到隊列,從硬盤查找圖片是否已被下載緩存。
- 根據(jù) URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。這一步是在 NSOperation 進(jìn)行的操作,所以回主線程進(jìn)行結(jié)果回調(diào) notifyDelegate:。
- 如果上一操作從硬盤讀取到了圖片,將圖片添加到內(nèi)存緩存中(如果空閑內(nèi)存過小,會先清空內(nèi)存緩存)。SDImageCacheDelegate 回調(diào)
imageCache:didFindImage:forKey:userInfo:。進(jìn)而回調(diào)展示圖片。
- 如果從硬盤緩存目錄讀取不到圖片,說明所有緩存都不存在該圖片,需要下載圖片,回調(diào)
imageCache:didNotFindImageForKey:userInfo:。
- 共享或重新生成一個下載器 SDWebImageDownloader 開始下載圖片。
- 圖片下載由 NSURLConnection 來做,實現(xiàn)相關(guān) delegate 來判斷圖片下載中、下載完成和下載失敗。
- connection:didReceiveData: 中利用 ImageIO 做了按圖片下載進(jìn)度加載效果。
- connectionDidFinishLoading: 數(shù)據(jù)下載完成后交給 SDWebImageDecoder 做圖片解碼處理。
- 圖片解碼處理在一個 NSOperationQueue 完成,不會拖慢主線程 UI。如果有需要對下載的圖片進(jìn)行二次處理,最好也在這里完成,效率會好很多。
- 在主線程 notifyDelegateOnMainThreadWithInfo: 宣告解碼完成,
imageDecoder:didFinishDecodingImage:userInfo:回調(diào)給 SDWebImageDownloader。
imageDownloader:didFinishWithImage:回調(diào)給 SDWebImageManager 告知圖片下載完成。
- 通知所有的 downloadDelegates 下載完成,回調(diào)給需要的地方展示圖片。
- 將圖片保存到 SDImageCache 中,內(nèi)存緩存和硬盤緩存同時保存。
- 寫文件到硬盤在單獨 NSInvocationOperation 中完成,避免拖慢主線程。
- 如果是在iOS上運(yùn)行,SDImageCache 在初始化的時候會注冊notification 到 UIApplicationDidReceiveMemoryWarningNotification 以及 UIApplicationWillTerminateNotification,在內(nèi)存警告的時候清理內(nèi)存圖片緩存,應(yīng)用結(jié)束的時候清理過期圖片。
- SDWebImagePrefetcher 可以預(yù)先下載圖片,方便后續(xù)使用。
其他一些有關(guān)SDWebImage的資料可以參照
SDWebImage源碼解析之SDWebImageManager的注解
什么時候會造成內(nèi)存泄漏
- 對于在使用MRC內(nèi)存管理策略的年代,你創(chuàng)建了對象,或者
retain了對象后,并沒有對對象做release操作,會造成內(nèi)存泄漏
(以下針對包括ARC和MRC兩種內(nèi)存管理策略) - 對象之間的循環(huán)引用
- 對象和Block的循環(huán)引用
- 使用KVC和KVO,對象添加了觀察者,但是對象沒有在
- dealloc方法中移除觀察者 - 使用通知的時候,沒有移除觀察者
-
performSelector可能導(dǎo)致內(nèi)存泄漏,當(dāng)所執(zhí)行的selector有返回值,必須對返回值重新釋放。 -
NSTimer,定時器會不斷的給target發(fā)送消息,即timer持有了target,當(dāng)沒有調(diào)用[timer invalidate],target就不會被釋放掉。造成內(nèi)存泄漏。 - @try...@catch中,當(dāng)拋出了移除,移除對象則不能被釋放。
某些點可以參照ARC下內(nèi)存泄露的那些點
自動釋放池的原理
在iOS應(yīng)用開始的main方法中,就創(chuàng)建了全局autoreleasepool,之后所有的對象都存在于該自動釋放池中。當(dāng)然,我們可以在代碼的局部創(chuàng)建局部的autoreleasepool,使用@autoreleasepool{// your code}
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
在最開始的理解中,認(rèn)為對象的釋放是在autoreleasepool的大括號({})結(jié)束的時候釋放了對象。其實并不是這樣的。在自動釋放池真正釋放對象是在當(dāng)前的runloop迭代結(jié)束的時候,它能夠真正釋放的原因是由于---系統(tǒng)在每個runloop迭代中都加入了自動釋放池Push和Pop
Autorelease 原理 - AutoreleasePoolPage
在ARC中,自己寫一個@autoreleasepool{}會被翻譯成
void *context = objc_autoreleasePoolPush();
// {}中的代碼
objc_autoreleasePoolPop(context);

- AutoreleasePool沒有特定的數(shù)據(jù)結(jié)構(gòu),它是由一個或多個AutoreleasePoolPage結(jié)構(gòu)構(gòu)成的,它的組織方式是雙向鏈表,體現(xiàn)在結(jié)構(gòu)體中的指針parent和指針child。
- 每個
AutoreleasePoolPage都有對應(yīng)特定的線程,其中的thread指向的是當(dāng)前線程。- 指針
id *next指向的是當(dāng)前AutoreleasePool新增進(jìn)來的autorelease對象的下一個位置。- AutoreleasePoolPage每個對象會開辟4096字節(jié)內(nèi)存(也就是虛擬內(nèi)存一頁的大小),除了上面的實例變量所占空間,剩下的空間全部用來儲存
autorelease對象的地址。- 當(dāng)當(dāng)前的
AutoreleasePoolPage空間大小不足時,就會新創(chuàng)建一個AutoreleasePoolPage。
假設(shè)當(dāng)前的線程中只有一個AutoreleasePoolPage,并且已經(jīng)快存滿了autorelease對象,如下圖:

上圖的next指針下一個位置就是棧頂,當(dāng)加入一個新的autorelease對象時,會創(chuàng)建一個新的AutoreleasePoolPage并用指針child連接新的AutoreleasePoolPage,完成連接后,新的AutoreleasePoolPagenext指針會初始化棧底(begin)位置,新的autorelease對象又可以添加了,.
Autorelease 原理 - 釋放
當(dāng)調(diào)用objc_autoreleasePoolPush時,會向AutoreleasePoolPage中插入一個哨兵對象,值為nil(0),如下:

函數(shù)objc_autoreleasePoolPush的返回值是哨兵對象的地址,作為函數(shù)objc_autoreleasePoolPop的入?yún)ⅰ?/p>
所以會有如下操作:
- 根據(jù)哨兵的地址查找到page的地址
- 對晚于哨兵加入所有的
autorelease對象發(fā)送一個release通知,并且移動next指針到哨兵對象位置。 - 移除所有的next指針之后的
autorelease對象,可以跨多頁page

總結(jié):以上即autoreleasepool的基本原理,所有圖片均來自下方的鏈接
黑幕背后的Autorelease
Objective-C Autorelease Pool 的實現(xiàn)原理
nonatomic和atomic
這是保證屬性在自動生成getter/setter方法時,對setter方法是否加鎖,以確保線程安全。
atomic : 理論上的線程安全,會在生成setter方法時候加上鎖,確保在setter方法結(jié)束前只能有一個線程操作變量。也就是說,threadA正在修改變量,threadB就無法修改變量??梢源_保getter到的數(shù)據(jù)一定是完整的.- nonatmic : 沒有對
getter/setter進(jìn)行原子操作,可能產(chǎn)生getter方法沒有獲取到完整數(shù)據(jù)。比如,當(dāng)時threadA正在調(diào)用getter,此時threadB正在使用setter,假設(shè)對象的有幾個屬性,可能獲取的對象就只能修改成功了其中兩個屬性。
簡友提醒:
atomic并不能保證整個對象的線程安全,只能保證屬性的存取(getter/setter)是線程安全的。例如threadA正在修改變量a(setter),threadB等待threadA修改完成后,threadB修改變量a。此時threadA獲取(getter)變量a。變量a則為threadB修改后的值。故而破環(huán)了對象的線程安全。
優(yōu)缺點 : 但是添加了線程鎖,所以效率比較低,在單線程中,沒有必要使用原子性。atomic雖然保證了線程安全,nonatomic則相反,效率比較高。
喜歡請隨意
