如作為一個(gè)開發(fā)者,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要,這是一個(gè)我的iOS交流群:638302184,不管你是小白還是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經(jīng)驗(yàn),討論技術(shù), 大家一起交流學(xué)習(xí)成長!
群內(nèi)提供數(shù)據(jù)結(jié)構(gòu)與算法、底層進(jìn)階、swift、逆向、整合面試題等免費(fèi)資料
附上一份收集的各大廠面試題(附答案) ! 群文件直接獲取
各大廠面試題
推薦閱讀:iOS開發(fā)——BAT面試題合集(持續(xù)更新中)
Swift好多坑,一個(gè)人填不來,怎么辦
問身邊同事吧,又怕被暗笑技術(shù)差勁
1、如何追蹤app崩潰率,如何解決線上閃退
當(dāng)iOS設(shè)備上的App應(yīng)用閃退時(shí),操作系統(tǒng)會生成一個(gè)crash日志,保存在設(shè)備上。crash日志上有很多有用的信息,比如每個(gè)正在執(zhí)行線程的完整堆棧跟蹤信息和內(nèi)存映像,這樣就能夠通過解析這些信息進(jìn)而定位crash發(fā)生時(shí)的代碼邏輯,從而找到App閃退的原因。通常來說,crash產(chǎn)生來源于兩種問題:違反iOS系統(tǒng)規(guī)則導(dǎo)致的crash和App代碼邏輯BUG導(dǎo)致的crash,下面分別對他們進(jìn)行分析。
1.1、違反iOS系統(tǒng)規(guī)則產(chǎn)生crash的三種類型
(1) 內(nèi)存報(bào)警閃退
當(dāng)iOS檢測到內(nèi)存過低時(shí),它的VM系統(tǒng)會發(fā)出低內(nèi)存警告通知,嘗試回收一些內(nèi)存;如果情況沒有得到足夠的改善,iOS會終止后臺應(yīng)用以回收更多內(nèi)存;最后,如果內(nèi)存還是不足,那么正在運(yùn)行的應(yīng)用可能會被終止掉。在Debug模式下,可以主動將客戶端執(zhí)行的動作邏輯寫入一個(gè)log文件中,這樣程序童鞋可以將內(nèi)存預(yù)警的邏輯寫入該log文件,當(dāng)發(fā)生如下截圖中的內(nèi)存報(bào)警時(shí),就是提醒當(dāng)前客戶端性能內(nèi)存吃緊,可以通過Instruments工具中的Allocations 和 Leaks模塊庫來發(fā)現(xiàn)內(nèi)存分配問題和內(nèi)存泄漏問題。
(2) 響應(yīng)超時(shí)
當(dāng)應(yīng)用程序?qū)σ恍┨囟ǖ氖录ū热鐔?、掛起、恢?fù)、結(jié)束)響應(yīng)不及時(shí),蘋果的Watchdog機(jī)制會把應(yīng)用程序干掉,并生成一份相應(yīng)的crash日志。這些事件與下列UIApplicationDelegate方法相對應(yīng),當(dāng)遇到Watchdog日志時(shí),可以檢查上圖中的幾個(gè)方法是否有比較重的阻塞UI的動作。
application:didFinishLaunchingWithOptions:
applicationWillResignActive:
applicationDidEnterBackground:
applicationWillEnterForeground:
applicationDidBecomeActive:
applicationWillTerminate:
(3) 用戶強(qiáng)制退出
一看到“用戶強(qiáng)制退出”,首先可能想到的雙擊Home鍵,然后關(guān)閉應(yīng)用程序。不過這種場景一般是不會產(chǎn)生crash日志的,因?yàn)殡p擊Home鍵后,所有的應(yīng)用程序都處于后臺狀態(tài),而iOS隨時(shí)都有可能關(guān)閉后臺進(jìn)程,當(dāng)應(yīng)用阻塞界面并停止響應(yīng)時(shí)這種場景才會產(chǎn)生crash日志。這里指的“用戶強(qiáng)制退出”場景,是稍微比較復(fù)雜點(diǎn)的操作:先按住電源鍵,直到出現(xiàn)“滑動關(guān)機(jī)”的界面時(shí),再按住Home鍵,這時(shí)候當(dāng)前應(yīng)用程序會被終止掉,并且產(chǎn)生一份相應(yīng)事件的crash日志。
1.2、應(yīng)用邏輯的Bug
大多數(shù)閃退崩潰日志的產(chǎn)生都是因?yàn)閼?yīng)用中的Bug,這種Bug的錯(cuò)誤種類有很多,比如
- SEGV:(Segmentation Violation,段違例),無效內(nèi)存地址,比如空指針,未初始化指針,棧溢出等;
- SIGABRT:收到Abort信號,可能自身調(diào)用abort()或者收到外部發(fā)送過來的信號;
- SIGBUS:總線錯(cuò)誤。與SIGSEGV不同的是,SIGSEGV訪問的是無效地址(比如虛存映射不到物理內(nèi)存),而SIGBUS訪問的是有效地址,但總線訪問異常(比如地址對齊問題);
- SIGILL:嘗試執(zhí)行非法的指令,可能不被識別或者沒有權(quán)限;
- SIGFPE:Floating Point Error,數(shù)學(xué)計(jì)算相關(guān)問題(可能不限于浮點(diǎn)計(jì)算),比如除零操作;
- SIGPIPE:管道另一端沒有進(jìn)程接手?jǐn)?shù)據(jù);
常見的崩潰原因基本都是代碼邏輯問題或資源問題,比如數(shù)組越界,訪問野指針或者資源不存在,或資源大小寫錯(cuò)誤等。
1.3、crash的收集
如果是在windows上你可以通過itools或pp助手等輔助工具查看系統(tǒng)產(chǎn)生的歷史crash日志,然后再根據(jù)app來查看。如果是在Mac 系統(tǒng)上,只需要打開xcode->windows->devices,選擇device logs進(jìn)行查看,如下圖,這些crash文件都可以導(dǎo)出來,然后再單獨(dú)對這個(gè)crash文件做處理分析。
<ignore_js_op>[圖片上傳中...(image-b1ea04-1554465565925-3)]</ignore_js_op>
看日志
市場上已有的商業(yè)軟件提供crash收集服務(wù),這些軟件基本都提供了日志存儲,日志符號化解析和服務(wù)端可視化管理等服務(wù):
開源的軟件也可以拿來收集crash日志,比如Razor,QuincyKit(git鏈接)等,這些軟件收集crash的原理其實(shí)大同小異,都是根據(jù)系統(tǒng)產(chǎn)生的crash日志進(jìn)行了一次提取或封裝,然后將封裝后的crash文件上傳到對應(yīng)的服務(wù)端進(jìn)行解析處理。很多商業(yè)軟件都采用了Plcrashreporter這個(gè)開源工具來上傳和解析crash,比如HockeyApp,Flurry和crittercism等。
<ignore_js_op>[圖片上傳中...(image-8de82e-1554465565925-2)]</ignore_js_op>
crash信息
由于自己的crash信息太長,找了一張示例:
1)crash標(biāo)識是應(yīng)用進(jìn)程產(chǎn)生crash時(shí)的一些標(biāo)識信息,它描述了該crash的唯一標(biāo)識(E838FEFB-ECF6-498C-8B35-D40F0F9FEAE4),所發(fā)生的硬件設(shè)備類型(iphone3,1代表iphone4),以及App進(jìn)程相關(guān)的信息等;
2)基本信息描述的是crash發(fā)生的時(shí)間和系統(tǒng)版本;
3)異常類型描述的是crash發(fā)生時(shí)拋出的異常類型和錯(cuò)誤碼;
4)線程回溯描述了crash發(fā)生時(shí)所有線程的回溯信息,每個(gè)線程在每一幀對應(yīng)的函數(shù)調(diào)用信息(這里由于空間限制沒有全部列出);
5)二進(jìn)制映像是指crash發(fā)生時(shí)已加載的二進(jìn)制文件。以上就是一份crash日志包含的所有信息,接下來就需要根據(jù)這些信息去解析定位導(dǎo)致crash發(fā)生的代碼邏輯, 這就需要用到符號化解析的過程(洋名叫:symbolication)。
1.4、解決線上閃退
首先保證,發(fā)布前充分測試。發(fā)布后依然有閃退現(xiàn)象,查看崩潰日志,及時(shí)修復(fù)并發(fā)布。
2、什么是事件響應(yīng)鏈,點(diǎn)擊屏幕時(shí)是如何互動的,事件的傳遞。
<ignore_js_op>[圖片上傳中...(image-7b6265-1554465565925-1)]</ignore_js_op>
事件響應(yīng)鏈
對于iOS設(shè)備用戶來說,他們操作設(shè)備的方式主要有三種:觸摸屏幕、晃動設(shè)備、通過遙控設(shè)施控制設(shè)備。對應(yīng)的事件類型有以下三種:
1、觸屏事件(Touch Event)
2、運(yùn)動事件(Motion Event)
3、遠(yuǎn)端控制事件(Remote-Control Event)
2.1、響應(yīng)者鏈(Responder Chain)
響應(yīng)者對象(Responder Object),指的是有響應(yīng)和處理事件能力的對象。響應(yīng)者鏈就是由一系列的響應(yīng)者對象構(gòu)成的一個(gè)層次結(jié)構(gòu)。
UIResponder是所有響應(yīng)對象的基類,在UIResponder類中定義了處理上述各種事件的接口。我們熟悉的UIApplication、 UIViewController、UIWindow和所有繼承自UIView的UIKit類都直接或間接的繼承自UIResponder,所以它們的實(shí)例都是可以構(gòu)成響應(yīng)者鏈的響應(yīng)者對象。
響應(yīng)者鏈有以下特點(diǎn):
1、響應(yīng)者鏈通常是由視圖(UIView)構(gòu)成的;
2、一個(gè)視圖的下一個(gè)響應(yīng)者是它視圖控制器(UIViewController)(如果有的話),然后再轉(zhuǎn)給它的父視圖(Super View);
3、視圖控制器(如果有的話)的下一個(gè)響應(yīng)者為其管理的視圖的父視圖;
4、單例的窗口(UIWindow)的內(nèi)容視圖將指向窗口本身作為它的下一個(gè)響應(yīng)者
需要指出的是,Cocoa Touch應(yīng)用不像Cocoa應(yīng)用,它只有一個(gè)UIWindow對象,因此整個(gè)響應(yīng)者鏈要簡單一點(diǎn);
5、單例的應(yīng)用(UIApplication)是一個(gè)響應(yīng)者鏈的終點(diǎn),它的下一個(gè)響應(yīng)者指向nil,以結(jié)束整個(gè)循環(huán)。
2.2、點(diǎn)擊屏幕時(shí)是如何互動的
iOS系統(tǒng)檢測到手指觸摸(Touch)操作時(shí)會將其打包成一個(gè)UIEvent對象,并放入當(dāng)前活動Application的事件隊(duì)列,單例的UIApplication會從事件隊(duì)列中取出觸摸事件并傳遞給單例的UIWindow來處理,UIWindow對象首先會使用hitTest:withEvent:方法尋找此次Touch操作初始點(diǎn)所在的視圖(View),即需要將觸摸事件傳遞給其處理的視圖,這個(gè)過程稱之為hit-test view。
UIWindow實(shí)例對象會首先在它的內(nèi)容視圖上調(diào)用hitTest:withEvent:,此方法會在其視圖層級結(jié)構(gòu)中的每個(gè)視圖上調(diào)用pointInside:withEvent:(該方法用來判斷點(diǎn)擊事件發(fā)生的位置是否處于當(dāng)前視圖范圍內(nèi),以確定用戶是不是點(diǎn)擊了當(dāng)前視圖),如果pointInside:withEvent:返回YES,則繼續(xù)逐級調(diào)用,直到找到touch操作發(fā)生的位置,這個(gè)視圖也就是要找的hit-test view。
hitTest:withEvent:方法的處理流程如下:首先調(diào)用當(dāng)前視圖的pointInside:withEvent:方法判斷觸摸點(diǎn)是否在當(dāng)前視圖內(nèi);若返回NO,則hitTest:withEvent:返回nil;若返回YES,則向當(dāng)前視圖的所有子視圖(subviews)發(fā)送hitTest:withEvent:消息,所有子視圖的遍歷順序是從最頂層視圖一直到到最底層視圖,即從subviews數(shù)組的末尾向前遍歷,直到有子視圖返回非空對象或者全部子視圖遍歷完畢;若第一次有子視圖返回非空對象,則hitTest:withEvent:方法返回此對象,處理結(jié)束;如所有子視圖都返回非,則hitTest:withEvent:方法返回自身(self)。
事件的傳遞和響應(yīng)分兩個(gè)鏈:
傳遞鏈:由系統(tǒng)向離用戶最近的view傳遞。UIKit –> active app’s event queue –> window –> root view –>……–>lowest view
響應(yīng)鏈:由離用戶最近的view向系統(tǒng)傳遞。initial view –> super view –> …..–> view controller –> window –> Application
3、Run Loop是什么,使用的目的,何時(shí)使用和關(guān)注點(diǎn)
Run Loop是一讓線程能隨時(shí)處理事件但不退出的機(jī)制。RunLoop 實(shí)際上是一個(gè)對象,這個(gè)對象管理了其需要處理的事件和消息,并提供了一個(gè)入口函數(shù)來執(zhí)行Event Loop 的邏輯。線程執(zhí)行了這個(gè)函數(shù)后,就會一直處于這個(gè)函數(shù)內(nèi)部 "接受消息->等待->處理" 的循環(huán)中,直到這個(gè)循環(huán)結(jié)束(比如傳入 quit 的消息),函數(shù)返回。讓線程在沒有處理消息時(shí)休眠以避免資源占用、在有消息到來時(shí)立刻被喚醒。
OSX/iOS 系統(tǒng)中,提供了兩個(gè)這樣的對象:NSRunLoop 和 CFRunLoopRef。CFRunLoopRef 是在 CoreFoundation 框架內(nèi)的,它提供了純 C 函數(shù)的 API,所有這些 API 都是線程安全的。NSRunLoop 是基于 CFRunLoopRef 的封裝,提供了面向?qū)ο蟮?API,但是這些 API 不是線程安全的。
線程和 RunLoop 之間是一一對應(yīng)的,其關(guān)系是保存在一個(gè)全局的 Dictionary 里。線程剛創(chuàng)建時(shí)并沒有 RunLoop,如果你不主動獲取,那它一直都不會有。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(shí),RunLoop 的銷毀是發(fā)生在線程結(jié)束時(shí)。你只能在一個(gè)線程的內(nèi)部獲取其 RunLoop(主線程除外)。
3.1、系統(tǒng)默認(rèn)注冊了5個(gè)Mode:
- kCFRunLoopDefaultMode: App的默認(rèn) Mode,通常主線程是在這個(gè) Mode 下運(yùn)行的。
- UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時(shí)不受其他 Mode 影響。
- UIInitializationRunLoopMode: 在剛啟動 App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動完成后就不再使用。
- GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到。
- kCFRunLoopCommonModes: 這是一個(gè)占位的 Mode,沒有實(shí)際作用。
3.2、Run Loop的四個(gè)作用:
- 使程序一直運(yùn)行接受用戶輸入
- 決定程序在何時(shí)應(yīng)該處理哪些Event
- 調(diào)用解耦
- 節(jié)省CPU時(shí)間
主線程的run loop默認(rèn)是啟動的。iOS的應(yīng)用程序里面,程序啟動后會有一個(gè)如下的main() 函數(shù):
int main(int argc, char *argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([appDelegate class]));
}
}
重點(diǎn)是UIApplicationMain() 函數(shù),這個(gè)方法會為main thread 設(shè)置一個(gè)NSRunLoop 對象,這就解釋了本文開始說的為什么我們的應(yīng)用可以在無人操作的時(shí)候休息,需要讓它干活的時(shí)候又能立馬響應(yīng)。
對其它線程來說,run loop默認(rèn)是沒有啟動的,如果你需要更多的線程交互則可以手動配置和啟動,如果線程只是去執(zhí)行一個(gè)長時(shí)間的已確定的任務(wù)則不需要。在任何一個(gè)Cocoa程序的線程中,都可以通過:
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
來獲取到當(dāng)前線程的run loop。
一個(gè)run loop就是一個(gè)事件處理循環(huán),用來不停的監(jiān)聽和處理輸入事件并將其分配到對應(yīng)的目標(biāo)上進(jìn)行處理。
NSRunLoop是一種更加高明的消息處理模式,他就高明在對消息處理過程進(jìn)行了更好的抽象和封裝,這樣才能是的你不用處理一些很瑣碎很低層次的具體消息的處理,在NSRunLoop中每一個(gè)消息就被打包在input source或者是timer source中了。使用run loop可以使你的線程在有工作的時(shí)候工作,沒有工作的時(shí)候休眠,這可以大大節(jié)省系統(tǒng)資源。
<ignore_js_op>[圖片上傳中...(image-d3574b-1554465565925-0)]</ignore_js_op>
RunLoop
3.3、什么時(shí)候使用run loop
僅當(dāng)在為你的程序創(chuàng)建輔助線程的時(shí)候,你才需要顯式運(yùn)行一個(gè)run loop。Run loop是程序主線程基礎(chǔ)設(shè)施的關(guān)鍵部分。所以,Cocoa和Carbon程序提供了代碼運(yùn)行主程序的循環(huán)并自動啟動run loop。IOS程序中UIApplication的run方法(或Mac OS X中的NSApplication)作為程序啟動步驟的一部分,它在程序正常啟動的時(shí)候就會啟動程序的主循環(huán)。類似的,RunApplicationEventLoop函數(shù)為Carbon程序啟動主循環(huán)。如果你使用xcode提供的模板創(chuàng)建你的程序,那你永遠(yuǎn)不需要自己去顯式的調(diào)用這些例程。
對于輔助線程,你需要判斷一個(gè)run loop是否是必須的。如果是必須的,那么你要自己配置并啟動它。你不需要在任何情況下都去啟動一個(gè)線程的run loop。比如,你使用線程來處理一個(gè)預(yù)先定義的長時(shí)間運(yùn)行的任務(wù)時(shí),你應(yīng)該避免啟動run loop。Run loop在你要和線程有更多的交互時(shí)才需要,比如以下情況:
使用端口或自定義輸入源來和其他線程通信
使用線程的定時(shí)器
Cocoa中使用任何performSelector…的方法
使線程周期性工作
3.4、關(guān)注點(diǎn)
3.4.1、Cocoa中的NSRunLoop類并不是線程安全的
我們不能再一個(gè)線程中去操作另外一個(gè)線程的run loop對象,那很可能會造成意想不到的后果。不過幸運(yùn)的是CoreFundation中的不透明類CFRunLoopRef是線程安全的,而且兩種類型的run loop完全可以混合使用。Cocoa中的NSRunLoop類可以通過實(shí)例方法:
- (CFRunLoopRef)getCFRunLoop;
獲取對應(yīng)的CFRunLoopRef類,來達(dá)到線程安全的目的。
3.4.2、Run loop的管理并不完全是自動的。
我們?nèi)员仨氃O(shè)計(jì)線程代碼以在適當(dāng)?shù)臅r(shí)候啟動run loop并正確響應(yīng)輸入事件,當(dāng)然前提是線程中需要用到run loop。而且,我們還需要使用while/for語句來驅(qū)動run loop能夠循環(huán)運(yùn)行,下面的代碼就成功驅(qū)動了一個(gè)run loop:
BOOL isRunning = NO;
do {
isRunning = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDatedistantFuture]];
} while (isRunning);
3.4.3、Run loop同時(shí)也負(fù)責(zé)autorelease pool的創(chuàng)建和釋放
在使用手動的內(nèi)存管理方式的項(xiàng)目中,會經(jīng)常用到很多自動釋放的對象,如果這些對象不能夠被即時(shí)釋放掉,會造成內(nèi)存占用量急劇增大。Run loop就為我們做了這樣的工作,每當(dāng)一個(gè)運(yùn)行循環(huán)結(jié)束的時(shí)候,它都會釋放一次autorelease pool,同時(shí)pool中的所有自動釋放類型變量都會被釋放掉。
4、ARC和MRC
Objective-c中提供了兩種內(nèi)存管理機(jī)制MRC(MannulReference Counting)和ARC(Automatic Reference Counting),分別提供對內(nèi)存的手動和自動管理,來滿足不同的需求。Xcode 4.1及其以前版本沒有ARC。
在MRC的內(nèi)存管理模式下,與對變量的管理相關(guān)的方法有:retain,release和autorelease。retain和release方法操作的是引用記數(shù),當(dāng)引用記數(shù)為零時(shí),便自動釋放內(nèi)存。并且可以用NSAutoreleasePool對象,對加入自動釋放池(autorelease調(diào)用)的變量進(jìn)行管理,當(dāng)drain時(shí)回收內(nèi)存。
(1) retain,該方法的作用是將內(nèi)存數(shù)據(jù)的所有權(quán)附給另一指針變量,引用數(shù)加1,即retainCount+= 1;
(2) release,該方法是釋放指針變量對內(nèi)存數(shù)據(jù)的所有權(quán),引用數(shù)減1,即retainCount-= 1;
(3) autorelease,該方法是將該對象內(nèi)存的管理放到autoreleasepool中。
在ARC中與內(nèi)存管理有關(guān)的標(biāo)識符,可以分為變量標(biāo)識符和屬性標(biāo)識符,對于變量默認(rèn)為__strong,而對于屬性默認(rèn)為unsafe_unretained。也存在autoreleasepool。
其中assign/retain/copy與MRC下property的標(biāo)識符意義相同,strong類似與retain,assign類似于unsafe_unretained,strong/weak/unsafe_unretained與ARC下變量標(biāo)識符意義相同,只是一個(gè)用于屬性的標(biāo)識,一個(gè)用于變量的標(biāo)識(帶兩個(gè)下劃短線__)。所列出的其他的標(biāo)識符與MRC下意義相同。
5、線程和進(jìn)程
進(jìn)程,是并發(fā)執(zhí)行的程序在執(zhí)行過程中分配和管理資源的基本單位,是一個(gè)動態(tài)概念,竟?fàn)幱?jì)算機(jī)系統(tǒng)資源的基本單位。每一個(gè)進(jìn)程都有一個(gè)自己的地址空間,即進(jìn)程空間或(虛空間)。進(jìn)程空間的大小 只與處理機(jī)的位數(shù)有關(guān),一個(gè) 16 位長處理機(jī)的進(jìn)程空間大小為 216 ,而 32 位處理機(jī)的進(jìn)程空間大小為 232 。進(jìn)程至少有 5 種基本狀態(tài),它們是:初始態(tài),執(zhí)行態(tài),等待狀態(tài),就緒狀態(tài),終止?fàn)顟B(tài)。
線程,在網(wǎng)絡(luò)或多用戶環(huán)境下,一個(gè)服務(wù)器通常需要接收大量且不確定數(shù)量用戶的并發(fā)請求,為每一個(gè)請求都創(chuàng)建一個(gè)進(jìn)程顯然是行不通的,——無論是從系統(tǒng)資源開銷方面或是響應(yīng)用戶請求的效率方面來看。因此,操作系統(tǒng)中線程的概念便被引進(jìn)了。線程,是進(jìn)程的一部分,一個(gè)沒有線程的進(jìn)程可以被看作是單線程的。線程有時(shí)又被稱為輕權(quán)進(jìn)程或輕量級進(jìn)程,也是 CPU 調(diào)度的一個(gè)基本單位。
進(jìn)程的執(zhí)行過程是線狀的,盡管中間會發(fā)生中斷或暫停,但該進(jìn)程所擁有的資源只為該線狀執(zhí)行過程服務(wù)。一旦發(fā)生進(jìn)程上下文切換,這些資源都是要被保護(hù)起來的。這是進(jìn)程宏觀上的執(zhí)行過程。而進(jìn)程又可有單線程進(jìn)程與多線程進(jìn)程兩種。我們知道,進(jìn)程有 一個(gè)進(jìn)程控制塊 PCB ,相關(guān)程序段 和 該程序段對其進(jìn)行操作的數(shù)據(jù)結(jié)構(gòu)集 這三部分,單線程進(jìn)程的執(zhí)行過程在宏觀上是線性的,微觀上也只有單一的執(zhí)行過程;而多線程進(jìn)程在宏觀上的執(zhí)行過程同樣為線性的,但微觀上卻可以有多個(gè)執(zhí)行操作(線程),如不同代碼片段以及相關(guān)的數(shù)據(jù)結(jié)構(gòu)集。線程的改變只代表了 CPU 執(zhí)行過程的改變,而沒有發(fā)生進(jìn)程所擁有的資源變化。除了 CPU 之外,計(jì)算機(jī)內(nèi)的軟硬件資源的分配與線程無關(guān),線程只能共享它所屬進(jìn)程的資源。與進(jìn)程控制表和 PCB 相似,每個(gè)線程也有自己的線程控制表 TCB ,而這個(gè) TCB 中所保存的線程狀態(tài)信息則要比 PCB 表少得多,這些信息主要是相關(guān)指針用堆棧(系統(tǒng)棧和用戶棧),寄存器中的狀態(tài)數(shù)據(jù)。進(jìn)程擁有一個(gè)完整的虛擬地址空間,不依賴于線程而獨(dú)立存在;反之,線程是進(jìn)程的一部分,沒有自己的地址空間,與進(jìn)程內(nèi)的其他線程一起共享分配給該進(jìn)程的所有資源。
線程可以有效地提高系統(tǒng)的執(zhí)行效率,但并不是在所有計(jì)算機(jī)系統(tǒng)中都是適用的,如某些很少做進(jìn)程調(diào)度和切換的實(shí)時(shí)系統(tǒng)。使用線程的好處是有多個(gè)任務(wù)需要處理機(jī)處理時(shí),減少處理機(jī)的切換時(shí)間;而且,線程的創(chuàng)建和結(jié)束所需要的系統(tǒng)開銷也比進(jìn)程的創(chuàng)建和結(jié)束要小得多。最適用使用線程的系統(tǒng)是多處理機(jī)系統(tǒng)和網(wǎng)絡(luò)系統(tǒng)或分布式系統(tǒng)。
6、平常常用的多線程處理方式及優(yōu)缺點(diǎn)
iOS有四種多線程編程的技術(shù),分別是:NSThread,Cocoa NSOperation,GCD(全稱:Grand Central Dispatch),pthread。
6.1、四種方式的優(yōu)缺點(diǎn)介紹:
1)NSThread優(yōu)點(diǎn):NSThread 比其他兩個(gè)輕量級。缺點(diǎn):需要自己管理線程的生命周期,線程同步。線程同步對數(shù)據(jù)的加鎖會有一定的系統(tǒng)開銷。
2)Cocoa NSOperation優(yōu)點(diǎn):不需要關(guān)心線程管理, 數(shù)據(jù)同步的事情,可以把精力放在自己需要執(zhí)行的操作上。Cocoa operation相關(guān)的類是NSOperation, NSOperationQueue.NSOperation是個(gè)抽象類,使用它必須用它的子類,可以實(shí)現(xiàn)它或者使用它定義好的兩個(gè)子類: NSInvocationOperation和NSBlockOperation.創(chuàng)建NSOperation子類的對象,把對象添加到NSOperationQueue隊(duì)列里執(zhí)行。
3)GCD(全優(yōu)點(diǎn)):Grand Central dispatch(GCD)是Apple開發(fā)的一個(gè)多核編程的解決方案。在iOS4.0開始之后才能使用。GCD是一個(gè)替代NSThread, NSOperationQueue,NSInvocationOperation等技術(shù)的很高效強(qiáng)大的技術(shù)。
4)pthread是一套通用的多線程API,適用于Linux\Windows\Unix,跨平臺,可移植,使用C語言,生命周期需要程序員管理,iOS開發(fā)中使用很少。
6.2、GCD線程死鎖
GCD 確實(shí)好用 ,很強(qiáng)大,相比NSOpretion 無法提供 取消任務(wù)的功能。
如此強(qiáng)大的工具用不好可能會出現(xiàn)線程死鎖。 如下代碼:
- (void)viewDidLoad{
[super viewDidLoad];
NSLog(@"=================4");
dispatch_sync(dispatch_get_main_queue(),
^{ NSLog(@"=================5"); });
NSLog(@"=================6");
}
6.3、GCD Queue 分為三種:
1,The main queue :主隊(duì)列,主線程就是在個(gè)隊(duì)列中。
2,Global queues : 全局并發(fā)隊(duì)列。
3,用戶隊(duì)列:是用函數(shù) dispatch_queue_create創(chuàng)建的自定義隊(duì)列
6.4、dispatch_sync 和 dispatch_async 區(qū)別:
dispatch_async(queue,block) async 異步隊(duì)列,dispatch_async
函數(shù)會立即返回, block會在后臺異步執(zhí)行。
dispatch_sync(queue,block) sync 同步隊(duì)列,dispatch_sync
函數(shù)不會立即返回,及阻塞當(dāng)前線程,等待 block同步執(zhí)行完成。
6.5、分析上面代碼:
viewDidLoad 在主線程中, 及在dispatch_get_main_queue() 中,執(zhí)行到sync 時(shí) 向
dispatch_get_main_queue()插入 同步 threed。sync 會等到 后面block 執(zhí)行完成才返回, sync 又再 dispatch_get_main_queue() 隊(duì)列中,它是串行隊(duì)列,sync 是后加入的,前一個(gè)是主線程,所以 sync 想執(zhí)行 block 必須等待主線程執(zhí)行完成,主線程等待 sync 返回,去執(zhí)行后續(xù)內(nèi)容。照成死鎖,sync 等待mainThread 執(zhí)行完成, mianThread 等待sync 函數(shù)返回。下面例子:
- (void)viewDidLoad{
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"=================1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"=================2"); });
NSLog(@"=================3"); });
}
程序會完成執(zhí)行,為什么不會出現(xiàn)死鎖。
首先: async 在主線程中 創(chuàng)建了一個(gè)異步線程 加入 全局并發(fā)隊(duì)列,async 不會等待block 執(zhí)行完成,立即返回,
1,async 立即返回, viewDidLoad 執(zhí)行完畢,及主線程執(zhí)行完畢。
2,同時(shí),全局并發(fā)隊(duì)列立即執(zhí)行異步 block , 打印 1, 當(dāng)執(zhí)行到 sync 它會等待 block 執(zhí)行完成才返回, 及等待dispatch_get_main_queue() 隊(duì)列中的 mianThread 執(zhí)行完成, 然后才開始調(diào)用block 。因?yàn)? 和 2 幾乎同時(shí)執(zhí)行,因?yàn)? 在全局并發(fā)隊(duì)列上, 2 中執(zhí)行到sync 時(shí) 1 可能已經(jīng)執(zhí)行完成或 等了一會,mainThread 很快退出, 2 等已執(zhí)行后繼續(xù)內(nèi)容。如果阻塞了主線程,2 中的sync 就無法執(zhí)行啦,mainThread 永遠(yuǎn)不會退出, sync 就永遠(yuǎn)等待著。
7、大量數(shù)據(jù)表的優(yōu)化方案
1.對查詢進(jìn)行優(yōu)化,要盡量避免全表掃描,首先應(yīng)考慮在 where 及 order by 涉及的列上建立索引。
2.應(yīng)盡量避免在 where 子句中對字段進(jìn)行 null 值判斷,否則將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描,如:
select id from t where num is null
最好不要給數(shù)據(jù)庫留NULL,盡可能的使用 NOT NULL填充數(shù)據(jù)庫.
備注、描述、評論之類的可以設(shè)置為 NULL,其他的,最好不要使用NULL。
不要以為 NULL 不需要空間,比如:char(100) 型,在字段建立時(shí),空間就固定了, 不管是否插入值(NULL也包含在內(nèi)),都是占用 100個(gè)字符的空間的,如果是varchar這樣的變長字段, null 不占用空間。
可以在num上設(shè)置默認(rèn)值0,確保表中num列沒有null值,然后這樣查詢:
select id from t where num=0
3.應(yīng)盡量避免在 where 子句中使用 != 或 <> 操作符,否則將引擎放棄使用索引而進(jìn)行全表掃描。
4.應(yīng)盡量避免在 where 子句中使用 or 來連接條件,如果一個(gè)字段有索引,一個(gè)字段沒有索引,將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描,如:
select id from t where num=10 or Name='admin'
可以這樣查詢:
select id from t where num=10 union all select id from t where Name='admin'
5.in 和 not in 也要慎用,否則會導(dǎo)致全表掃描,如:
select id from t where num in (1,2,3)
對于連續(xù)的數(shù)值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
很多時(shí)候用 exists 代替 in 是一個(gè)好的選擇:
select num from a where num in (select num from b)
用下面的語句替換:
select num from a where exists (select 1 from b where num=a.num)
6.下面的查詢也將導(dǎo)致全表掃描:
select id from t where name like '%abc%'
若要提高效率,可以考慮全文檢索。
7.如果在 where 子句中使用參數(shù),也會導(dǎo)致全表掃描。因?yàn)镾QL只有在運(yùn)行時(shí)才會解析局部變量,但優(yōu)化程序不能將訪問計(jì)劃的選擇推遲到運(yùn)行時(shí);它必須在編譯時(shí)進(jìn)行選擇。然 而,如果在編譯時(shí)建立訪問計(jì)劃,變量的值還是未知的,因而無法作為索引選擇的輸入項(xiàng)。如下面語句將進(jìn)行全表掃描:
select id from t where num=@num
可以改為強(qiáng)制查詢使用索引:
select id from t with (index(索引名)) where num=@num
8.應(yīng)盡量避免在 where 子句中對字段進(jìn)行表達(dá)式操作,這將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描。如:
select id from t where num/2=100
應(yīng)改為:
select id from t where num=100*2
9.應(yīng)盡量避免在where子句中對字段進(jìn)行函數(shù)操作,這將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描。如:
select id from t where substring(name,1,3)='abc' -–name以abc開頭的id
select id from t where datediff(day,createdate,'2015-11-30')=0 -–'2015-11-30' --生成的id
應(yīng)改為:
select id from t where name like'abc%'
select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'
10.不要在 where 子句中的“=”左邊進(jìn)行函數(shù)、算術(shù)運(yùn)算或其他表達(dá)式運(yùn)算,否則系統(tǒng)將可能無法正確使用索引。
11.在使用索引字段作為條件時(shí),如果該索引是復(fù)合索引,那么必須使用到該索引中的第一個(gè)字段作為條件時(shí)才能保證系統(tǒng)使用該索引,否則該索引將不會被使用,并且應(yīng)盡可能的讓字段順序與索引順序相一致。
12.不要寫一些沒有意義的查詢,如需要生成一個(gè)空表結(jié)構(gòu):
select col1,col2 into #t from t where1=0
這類代碼不會返回任何結(jié)果集,但是會消耗系統(tǒng)資源的,應(yīng)改成這樣:
create table #t(…)
13.Update 語句,如果只更改1、2個(gè)字段,不要Update全部字段,否則頻繁調(diào)用會引起明顯的性能消耗,同時(shí)帶來大量日志。
14.對于多張大數(shù)據(jù)量(這里幾百條就算大了)的表JOIN,要先分頁再JOIN,否則邏輯讀會很高,性能很差。
15.select count(*) from table;這樣不帶任何條件的count會引起全表掃描,并且沒有任何業(yè)務(wù)意義,是一定要杜絕的。
16.索引并不是越多越好,索引固然可以提高相應(yīng)的 select 的效率,但同時(shí)也降低了 insert 及 update 的效率,因?yàn)?insert 或 update 時(shí)有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個(gè)表的索引數(shù)最好不要超過6個(gè),若太多則應(yīng)考慮一些不常使用到的列上建的索引是否有 必要。
17.應(yīng)盡可能的避免更新 clustered 索引數(shù)據(jù)列,因?yàn)?clustered 索引數(shù)據(jù)列的順序就是表記錄的物理存儲順序,一旦該列值改變將導(dǎo)致整個(gè)表記錄的順序的調(diào)整,會耗費(fèi)相當(dāng)大的資源。若應(yīng)用系統(tǒng)需要頻繁更新 clustered 索引數(shù)據(jù)列,那么需要考慮是否應(yīng)將該索引建為 clustered 索引。
18.盡量使用數(shù)字型字段,若只含數(shù)值信息的字段盡量不要設(shè)計(jì)為字符型,這會降低查詢和連接的性能,并會增加存儲開銷。這是因?yàn)橐嬖谔幚聿樵兒瓦B 接時(shí)會逐個(gè)比較字符串中每一個(gè)字符,而對于數(shù)字型而言只需要比較一次就夠了。
19.盡可能的使用 varchar/nvarchar 代替 char/nchar ,因?yàn)槭紫茸冮L字段存儲空間小,可以節(jié)省存儲空間,其次對于查詢來說,在一個(gè)相對較小的字段內(nèi)搜索效率顯然要高些。
20.任何地方都不要使用
select * from t
用具體的字段列表代替“*”,不要返回用不到的任何字段。
21.盡量使用表變量來代替臨時(shí)表。如果表變量包含大量數(shù)據(jù),請注意索引非常有限(只有主鍵索引)。
22.避免頻繁創(chuàng)建和刪除臨時(shí)表,以減少系統(tǒng)表資源的消耗。臨時(shí)表并不是不可使用,適當(dāng)?shù)厥褂盟鼈兛梢允鼓承├谈行В?,?dāng)需要重復(fù)引用大型表或常用表中的某個(gè)數(shù)據(jù)集時(shí)。但是,對于一次性事件, 最好使用導(dǎo)出表。
23.在新建臨時(shí)表時(shí),如果一次性插入數(shù)據(jù)量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果數(shù)據(jù)量不大,為了緩和系統(tǒng)表的資源,應(yīng)先create table,然后insert。
24.如果使用到了臨時(shí)表,在存儲過程的最后務(wù)必將所有的臨時(shí)表顯式刪除,先 truncate table ,然后 drop table ,這樣可以避免系統(tǒng)表的較長時(shí)間鎖定。
25.盡量避免使用游標(biāo),因?yàn)橛螛?biāo)的效率較差,如果游標(biāo)操作的數(shù)據(jù)超過1萬行,那么就應(yīng)該考慮改寫。
26.使用基于游標(biāo)的方法或臨時(shí)表方法之前,應(yīng)先尋找基于集的解決方案來解決問題,基于集的方法通常更有效。
27.與臨時(shí)表一樣,游標(biāo)并不是不可使用。對小型數(shù)據(jù)集使用 FAST_FORWARD 游標(biāo)通常要優(yōu)于其他逐行處理方法,尤其是在必須引用幾個(gè)表才能獲得所需的數(shù)據(jù)時(shí)。在結(jié)果集中包括“合計(jì)”的例程通常要比使用游標(biāo)執(zhí)行的速度快。如果開發(fā)時(shí) 間允許,基于游標(biāo)的方法和基于集的方法都可以嘗試一下,看哪一種方法的效果更好。
28.在所有的存儲過程和觸發(fā)器的開始處設(shè)置 SET NOCOUNT ON ,在結(jié)束時(shí)設(shè)置 SET NOCOUNT OFF 。無需在執(zhí)行存儲過程和觸發(fā)器的每個(gè)語句后向客戶端發(fā)送 DONE_IN_PROC 消息。
29.盡量避免大事務(wù)操作,提高系統(tǒng)并發(fā)能力。
30.盡量避免向客戶端返回大數(shù)據(jù)量,若數(shù)據(jù)量過大,應(yīng)該考慮相應(yīng)需求是否合理。
實(shí)際案例分析:拆分大的 DELETE 或INSERT 語句,批量提交SQL語句
如果你需要在一個(gè)在線的網(wǎng)站上去執(zhí)行一個(gè)大的 DELETE 或 INSERT 查詢,你需要非常小心,要避免你的操作讓你的整個(gè)網(wǎng)站停止相應(yīng)。因?yàn)檫@兩個(gè)操作是會鎖表的,表一鎖住了,別的操作都進(jìn)不來了。
Apache 會有很多的子進(jìn)程或線程。所以,其工作起來相當(dāng)有效率,而我們的服務(wù)器也不希望有太多的子進(jìn)程,線程和數(shù)據(jù)庫鏈接,這是極大的占服務(wù)器資源的事情,尤其是內(nèi)存。
如果你把你的表鎖上一段時(shí)間,比如30秒鐘,那么對于一個(gè)有很高訪問量的站點(diǎn)來說,這30秒所積累的訪問進(jìn)程/線程,數(shù)據(jù)庫鏈接,打開的文件數(shù),可能不僅僅會讓你的WEB服務(wù)崩潰,還可能會讓你的整臺服務(wù)器馬上掛了。
所以,如果你有一個(gè)大的處理,你一定把其拆分,使用 LIMIT oracle(rownum),sqlserver(top)條件是一個(gè)好的方法。下面是一個(gè)mysql示例:
while(1){//每次只做1000條
mysql_query(“delete from logs where log_date <= ’2015-11-01’ limit 1000”);
if(mysql_affected_rows() == 0){//刪除完成,退出!break;
}//每次暫停一段時(shí)間,釋放表讓其他進(jìn)程/線程訪問。
usleep(50000)
}
8、常用到的動畫庫
Facebook 開源動畫庫 Pop 的 GitHub 主頁:facebook/pop · GitHub,介紹:Playing with Pop (i)
Canvas 項(xiàng)目主頁:Canvas - Simplify iOS Development,介紹:Animate in Xcode Without Code
拿 Canvas 來和 Pop 比其實(shí)不大合適,雖然兩者都自稱「動畫庫」,但是「庫」這個(gè)詞的含義有所區(qū)別。本質(zhì)上 Canvas 是一個(gè)「動畫合集」而 Pop 是一個(gè)「動畫引擎」。
先說 Canvas。Canvas 的目的是「Animate in Xcode Without Code」。開發(fā)者可以通過在 Storyboard 中指定 User Defined Runtime Attributes 來實(shí)現(xiàn)一些 Canvas 中預(yù)設(shè)的動畫,也就是他網(wǎng)站上能看到的那些。但是除了更改動畫的 delay 和 duration 基本上不能調(diào)整其他的參數(shù)。
Pop 就不一樣了。如果說 Canvas 是對 Core Animation 的封裝,Pop 則是對 Core Animation(以及 UIDynamics)的再實(shí)現(xiàn)。
Pop 語法上和 Core Animation 相似,效果上則不像 Canvas 那么生硬(時(shí)間四等分,振幅硬編碼)。這使得對 Core Animation 有了解的程序員可以很輕松地把原來的「靜態(tài)動畫」轉(zhuǎn)換成「動態(tài)動畫」。
同時(shí) Pop 又往前多走了一步。既然動畫的本質(zhì)是根據(jù)時(shí)間函數(shù)來做插值,那么理論上任何一個(gè)對象的任何一個(gè)值都可以用來做插值,而不僅僅是 Core Animation 里定死的那一堆大小、位移、旋轉(zhuǎn)、縮放等 animatable properties。
9、Restful架構(gòu)
REST是一種架構(gòu)風(fēng)格,其核心是面向資源,REST專門針對網(wǎng)絡(luò)應(yīng)用設(shè)計(jì)和開發(fā)方式,以降低開發(fā)的復(fù)雜性,提高系統(tǒng)的可伸縮性。REST提出設(shè)計(jì)概念和準(zhǔn)則為:
1.網(wǎng)絡(luò)上的所有事物都可以被抽象為資源(resource)
2.每一個(gè)資源都有唯一的資源標(biāo)識(resource identifier),對資源的操作不會改變這些標(biāo)識
3.所有的操作都是無狀態(tài)的
REST簡化開發(fā),其架構(gòu)遵循CRUD原則,該原則告訴我們對于資源(包括網(wǎng)絡(luò)資源)只需要四種行為:創(chuàng)建,獲取,更新和刪除就可以完成相關(guān)的操作和處理。您可以通過統(tǒng)一資源標(biāo)識符(Universal Resource Identifier,URI)來識別和定位資源,并且針對這些資源而執(zhí)行的操作是通過 HTTP 規(guī)范定義的。其核心操作只有GET,PUT,POST,DELETE。
由于REST強(qiáng)制所有的操作都必須是stateless的,這就沒有上下文的約束,如果做分布式,集群都不需要考慮上下文和會話保持的問題。極大的提高系統(tǒng)的可伸縮性。
RESTful架構(gòu):
(1)每一個(gè)URI代表一種資源;
(2)客戶端和服務(wù)器之間,傳遞這種資源的某種表現(xiàn)層;
(3)客戶端通過四個(gè)HTTP動詞,對服務(wù)器端資源進(jìn)行操作,實(shí)現(xiàn)"表現(xiàn)層狀態(tài)轉(zhuǎn)化"。
10、請分析下SDWebImage的原理
這個(gè)類庫提供一個(gè)UIImageView類別以支持加載來自網(wǎng)絡(luò)的遠(yuǎn)程圖片。具有緩存管理、異步下載、同一個(gè)URL下載次數(shù)控制和優(yōu)化等特征。
10.1、SDWebImage 加載圖片的流程
1.入口 setImageWithURL:placeholderImage : options: 會先把 placeholderImage 顯示,然后 SDWebImageManager 根據(jù) URL 開始處理圖片。
2.進(jìn)入 SDWebImageManager-downloadWithURL:delegate: options:userInfo:,交給 SDImageCache 從緩存查找圖片是否已經(jīng)下載 queryDiskCacheForKey: delegate:userInfo:.
3.先從內(nèi)存圖片緩存查找是否有圖片,如果內(nèi)存中已經(jīng)有圖片緩存,SDImageCacheDelegate 回調(diào) imageCache:didFindImage: forKey:userInfo: 到 SDWebImageManager。
4.SDWebImageManagerDelegate 回調(diào) webImageManager: didFinishWithImage: 到 UIImageView+WebCache 等前端展示圖片。
5.如果內(nèi)存緩存中沒有,生成 NSInvocationOperation 添加到隊(duì)列開始從硬盤查找圖片是否已經(jīng)緩存。
6.根據(jù) URLKey 在硬盤緩存目錄下嘗試讀取圖片文件。這一步是在 NSOperation 進(jìn)行的操作,所以回主線程進(jìn)行結(jié)果回調(diào) notifyDelegate:。
7.如果上一操作從硬盤讀取到了圖片,將圖片添加到內(nèi)存緩存中(如果空閑內(nèi)存過小,會先清空內(nèi)存緩存)。SDImageCacheDelegate 回調(diào) imageCache: didFindImage: forKey: userInfo:。進(jìn)而回調(diào)展示圖片。
8.如果從硬盤緩存目錄讀取不到圖片,說明所有緩存都不存在該圖片,需要下載圖片,回調(diào) imageCache: didNotFindImageForKey: userInfo:。
9.共享或重新生成一個(gè)下載器 SDWebImageDownloader 開始下載圖片。
10.圖片下載由 NSURLConnection 來做,實(shí)現(xiàn)相關(guān) delegate 來判斷圖片下載中、下載完成和下載失敗。
11.connection: didReceiveData: 中利用 ImageIO 做了按圖片下載進(jìn)度加載效果。
12.connectionDidFinishLoading: 數(shù)據(jù)下載完成后交給 SDWebImageDecoder 做圖片解碼處理。
13.圖片解碼處理在一個(gè) NSOperationQueue 完成,不會拖慢主線程 UI。如果有需要對下載的圖片進(jìn)行二次處理,最好也在這里完成,效率會好很多。
14.在主線程 notifyDelegateOnMainThreadWithInfo: 宣告解碼完成,imageDecoder:didFinishDecodingImage: userInfo: 回調(diào)給 SDWebImageDownloader。
15.imageDownloader: didFinishWithImage: 回調(diào)給 SDWebImageManager 告知圖片下載完成。
16.通知所有的 downloadDelegates 下載完成,回調(diào)給需要的地方展示圖片。
17.將圖片保存到 SDImageCache 中,內(nèi)存緩存和硬盤緩存同時(shí)保存。寫文件到硬盤也在以單獨(dú) NSInvocationOperation 完成,避免拖慢主線程。
18.SDImageCache 在初始化的時(shí)候會注冊一些消息通知,在內(nèi)存警告或退到后臺的時(shí)候清理內(nèi)存圖片緩存,應(yīng)用結(jié)束的時(shí)候清理過期圖片。
19.SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache,方便使用。
20.SDWebImagePrefetcher 可以預(yù)先下載圖片,方便后續(xù)使用。
10.2、SDWebImage庫的作用
通過對UIImageView的類別擴(kuò)展來實(shí)現(xiàn)異步加載替換圖片的工作。
主要用到的對象:
1、UIImageView (WebCache)類別,入口封裝,實(shí)現(xiàn)讀取圖片完成后的回調(diào)
2、SDWebImageManager,對圖片進(jìn)行管理的中轉(zhuǎn)站,記錄那些圖片正在讀取。
向下層讀取Cache(調(diào)用SDImageCache),或者向網(wǎng)絡(luò)讀取對象(調(diào)用SDWebImageDownloader) 。
實(shí)現(xiàn)SDImageCache和SDWebImageDownloader的回調(diào)。
3、SDImageCache,根據(jù)URL的MD5摘要對圖片進(jìn)行存儲和讀?。▽?shí)現(xiàn)存在內(nèi)存中或者存在硬盤上兩種實(shí)現(xiàn))
實(shí)現(xiàn)圖片和內(nèi)存清理工作。
4、SDWebImageDownloader,根據(jù)URL向網(wǎng)絡(luò)讀取數(shù)據(jù)(實(shí)現(xiàn)部分讀取和全部讀取后再通知回調(diào)兩種方式)
其他類:
SDWebImageDecoder,異步對圖像進(jìn)行了一次解壓??
1、SDImageCache是怎么做數(shù)據(jù)管理的?
SDImageCache分兩個(gè)部分,一個(gè)是內(nèi)存層面的,一個(gè)是硬盤層面的。內(nèi)存層面的相當(dāng)是個(gè)緩存器,以Key-Value的形式存儲圖片。當(dāng)內(nèi)存不夠的時(shí)候會清除所有緩存圖片。用搜索文件系統(tǒng)的方式做管理,文件替換方式是以時(shí)間為單位,剔除時(shí)間大于一周的圖片文件。當(dāng)SDWebImageManager向SDImageCache要資源時(shí),先搜索內(nèi)存層面的數(shù)據(jù),如果有直接返回,沒有的話去訪問磁盤,將圖片從磁盤讀取出來,然后做Decoder,將圖片對象放到內(nèi)存層面做備份,再返回調(diào)用層。
2、為啥必須做Decoder?
由于UIImage的imageWithData函數(shù)是每次畫圖的時(shí)候才將Data解壓成ARGB的圖像,所以在每次畫圖的時(shí)候,會有一個(gè)解壓操作,這樣效率很低,但是只有瞬時(shí)的內(nèi)存需求。為了提高效率通過SDWebImageDecoder將包裝在Data下的資源解壓,然后畫在另外一張圖片上,這樣這張新圖片就不再需要重復(fù)解壓了。
這種做法是典型的空間換時(shí)間的做法。
文章來源網(wǎng)絡(luò)