1.如何追蹤app崩潰率配椭,如何解決線上閃退
當(dāng) iOS設(shè)備上的App應(yīng)用閃退時(shí)虫溜,操作系統(tǒng)會(huì)生成一個(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)行分析迷守。
2.違反iOS系統(tǒng)規(guī)則產(chǎn)生crash的三種類型:
(1) 內(nèi)存報(bào)警閃退
當(dāng) iOS檢測到內(nèi)存過低時(shí)犬绒,它的VM系統(tǒng)會(huì)發(fā)出低內(nèi)存警告通知诡曙,嘗試回收一些內(nèi)存柄错;如果情況沒有得到足夠的改善,iOS會(huì)終止后臺(tái)應(yīng)用以回收更多內(nèi)存饵婆;最 后礼华,如果內(nèi)存還是不足咐鹤,那么正在運(yùn)行的應(yīng)用可能會(huì)被終止掉。在Debug模式下卓嫂,可以主動(dòng)將客戶端執(zhí)行的動(dòng)作邏輯寫入一個(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ū)σ恍┨囟ǖ?事件(比如啟動(dòng)餐禁、掛起血久、恢復(fù)、結(jié)束)響應(yīng)不及時(shí)帮非,蘋果的Watchdog機(jī)制會(huì)把應(yīng)用程序干掉氧吐,并生成一份相應(yīng)的crash日志。這些事件與下列 UIApplicationDelegate方法相對應(yīng)末盔,當(dāng)遇到Watchdog日志時(shí)筑舅,可以檢查上圖中的幾個(gè)方法是否有比較重的阻塞UI的動(dòng)作。
1
2
3
4
5
6
application:didFinishLaunchingWithOptions:
applicationWillResignActive:
applicationDidEnterBackground:
applicationWillEnterForeground:
applicationDidBecomeActive:
applicationWillTerminate:
(3) 用戶強(qiáng)制退出
一 看到“用戶強(qiáng)制退出”陨舱,首先可能想到的雙擊Home鍵翠拣,然后關(guān)閉應(yīng)用程序。不過這種場景一般是不會(huì)產(chǎn)生crash日志的游盲,因?yàn)殡p擊Home鍵后误墓,所有的應(yīng) 用程序都處于后臺(tái)狀態(tài),而iOS隨時(shí)都有可能關(guān)閉后臺(tái)進(jìn)程益缎,當(dāng)應(yīng)用阻塞界面并停止響應(yīng)時(shí)這種場景才會(huì)產(chǎn)生crash日志谜慌。這里指的“用戶強(qiáng)制退出”場景, 是稍微比較復(fù)雜點(diǎn)的操作:先按住電源鍵莺奔,直到出現(xiàn)“滑動(dòng)關(guān)機(jī)”的界面時(shí)欣范,再按住Home鍵,這時(shí)候當(dāng)前應(yīng)用程序會(huì)被終止掉,并且產(chǎn)生一份相應(yīng)事件的 crash日志熙卡。
應(yīng)用邏輯的Bug
大多數(shù)閃退崩潰日志的產(chǎn)生都是因?yàn)閼?yīng)用中的Bug杖刷,這種Bug的錯(cuò)誤種類有很多,比如:
SEGV:(Segmentation Violation驳癌,段違例),無效內(nèi)存地址役听,比如空指針颓鲜,未初始化指針,棧溢出等典予;
SIGABRT:收到Abort信號(hào)甜滨,可能自身調(diào)用abort()或者收到外部發(fā)送過來的信號(hào);
SIGBUS:總線錯(cuò)誤瘤袖。與SIGSEGV不同的是衣摩,SIGSEGV訪問的是無效地址(比如虛存映射不到物理內(nèi)存),而SIGBUS訪問的是有效地址捂敌,但總線訪問異常(比如地址對齊問題)艾扮;
SIGILL:嘗試執(zhí)行非法的指令,可能不被識(shí)別或者沒有權(quán)限占婉;
SIGFPE:Floating Point Error泡嘴,數(shù)學(xué)計(jì)算相關(guān)問題(可能不限于浮點(diǎn)計(jì)算),比如除零操作逆济;
SIGPIPE:管道另一端沒有進(jìn)程接手?jǐn)?shù)據(jù)酌予;
常見的崩潰原因基本都是代碼邏輯問題或資源問題,比如數(shù)組越界奖慌,訪問野指針或者資源不存在抛虫,或資源大小寫錯(cuò)誤等简僧。
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文件做處理分析扭吁。
看日志
市場上已有的商業(yè)軟件提供crash收集服務(wù),這些軟件基本都提供了日志存儲(chǔ),日志符號(hào)化解析和服務(wù)端可視化管理等服務(wù):
Crashlytics (www.crashlytics.com)
Crittercism (www.crittercism.com)
Bugsense (www.bugsense.com)
HockeyApp (www.hockeyapp.net)
Flurry(www.flurry.com)
開 源的軟件也可以拿來收集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等九杂。
crash信息
由于自己的crash信息太長颁湖,找了一張示例:
1)crash 標(biāo)識(shí)是應(yīng)用進(jìn)程產(chǎn)生crash時(shí)的一些標(biāo)識(shí)信息,它描述了該crash的唯一標(biāo)識(shí)(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ā)生的代碼邏輯惶我, 這就需要用到符號(hào)化解析的過程(洋名叫:symbolication)妈倔。
解決線上閃退
首先保證,發(fā)布前充分測試绸贡。發(fā)布后依然有閃退現(xiàn)象盯蝴,查看崩潰日志,及時(shí)修復(fù)并發(fā)布听怕。
2.什么是事件響應(yīng)鏈捧挺,點(diǎn)擊屏幕時(shí)是如何互動(dòng)的,事件的傳遞尿瞭。
事件響應(yīng)鏈
對于IOS設(shè)備用戶來說闽烙,他們操作設(shè)備的方式主要有三種:觸摸屏幕、晃動(dòng)設(shè)備声搁、通過遙控設(shè)施控制設(shè)備黑竞。對應(yīng)的事件類型有以下三種:
1、觸屏事件(Touch Event)
2疏旨、運(yùn)動(dòng)事件(Motion Event)
3很魂、遠(yuǎn)端控制事件(Remote-Control Event)
響應(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)。
點(diǎn)擊屏幕時(shí)是如何互動(dòng)的
iOS 系統(tǒng)檢測到手指觸摸(Touch)操作時(shí)會(huì)將其打包成一個(gè)UIEvent對象舍败,并放入當(dāng)前活動(dòng)Application的事件隊(duì)列招狸,單例的 UIApplication會(huì)從事件隊(duì)列中取出觸摸事件并傳遞給單例的UIWindow來處理,UIWindow對象首先會(huì)使用 hitTest:withEvent:方法尋找此次Touch操作初始點(diǎn)所在的視圖(View)邻薯,即需要將觸摸事件傳遞給其處理的視圖裙戏,這個(gè)過程稱之為 hit-test view。
UIWindow實(shí)例對象會(huì)首先在它的內(nèi)容視圖上調(diào)用hitTest:withEvent:厕诡,此方法會(huì)在其視 圖層級結(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ù)后钩骇,就會(huì)一直處于這個(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够坐,如果你不主動(dòng)獲取,那它一直都不會(huì)有崖面。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(shí)元咙,RunLoop 的銷毀是發(fā)生在線程結(jié)束時(shí)。你只能在一個(gè)線程的內(nèi)部獲取其 RunLoop(主線程除外)巫员。
系統(tǒng)默認(rèn)注冊了5個(gè)Mode:
kCFRunLoopDefaultMode: App的默認(rèn) Mode庶香,通常主線程是在這個(gè) Mode 下運(yùn)行的。
UITrackingRunLoopMode: 界面跟蹤 Mode简识,用于 ScrollView 追蹤觸摸滑動(dòng)赶掖,保證界面滑動(dòng)時(shí)不受其他 Mode 影響感猛。
UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用奢赂。
GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode陪白,通常用不到。
kCFRunLoopCommonModes: 這是一個(gè)占位的 Mode膳灶,沒有實(shí)際作用咱士。
Run Loop的四個(gè)作用:
使程序一直運(yùn)行接受用戶輸入
決定程序在何時(shí)應(yīng)該處理哪些Event
調(diào)用解耦
節(jié)省CPU時(shí)間
主線程的run loop默認(rèn)是啟動(dòng)的。iOS的應(yīng)用程序里面轧钓,程序啟動(dòng)后會(huì)有一個(gè)如下的main() 函數(shù):
1
2
3
4
5
6
int main(int argc, char *argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([appDelegate class]));
}
}
重點(diǎn)是UIApplicationMain() 函數(shù)序厉,這個(gè)方法會(huì)為main thread 設(shè)置一個(gè)NSRunLoop 對象,這就解釋了本文開始說的為什么我們的應(yīng)用可以在無人操作的時(shí)候休息毕箍,需要讓它干活的時(shí)候又能立馬響應(yīng)弛房。
對其它線程來說,run loop默認(rèn)是沒有啟動(dòng)的而柑,如果你需要更多的線程交互則可以手動(dòng)配置和啟動(dòng)庭再,如果線程只是去執(zhí)行一個(gè)長時(shí)間的已確定的任務(wù)則不需要。在任何一個(gè)Cocoa程序的線程中牺堰,都可以通過:
1
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)資源。
RunLoop
什么時(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)并自動(dòng)啟動(dòng)run loop拖陆。IOS程序中UIApplication的run方法(或Mac OS X中的NSApplication)作為程序啟動(dòng)步驟的一部分,它在程序正常啟動(dòng)的時(shí)候就會(huì)啟動(dòng)程序的主循環(huán)懊亡。類似 的依啰,RunApplicationEventLoop函數(shù)為Carbon程序啟動(dòng)主循環(huán)。如果你使用xcode提供的模板創(chuàng)建你的程序店枣,那你永遠(yuǎn)不需要自 己去顯式的調(diào)用這些例程速警。
對于輔助線程叹誉,你需要判斷一個(gè)run loop是否是必須的。如果是必須的闷旧,那么你要自己配置并啟動(dòng)它长豁。你不需要在任何情況下都去啟動(dòng)一個(gè)線程的run loop。比如鸠匀,你使用線程來處理一個(gè)預(yù)先定義的長時(shí)間運(yùn)行的任務(wù)時(shí)蕉斜,你應(yīng)該避免啟動(dòng)run loop。Run loop在你要和線程有更多的交互時(shí)才需要缀棍,比如以下情況:
使用端口或自定義輸入源來和其他線程通信
使用線程的定時(shí)器
Cocoa中使用任何performSelector…的方法
使線程周期性工作
關(guān)注點(diǎn)
Cocoa中的NSRunLoop類并不是線程安全的
我 們不能再一個(gè)線程中去操作另外一個(gè)線程的run loop對象宅此,那很可能會(huì)造成意想不到的后果。不過幸運(yùn)的是CoreFundation中的不透明類CFRunLoopRef是線程安全的爬范,而且兩種類型 的run loop完全可以混合使用父腕。Cocoa中的NSRunLoop類可以通過實(shí)例方法:
1
- (CFRunLoopRef)getCFRunLoop;
獲取對應(yīng)的CFRunLoopRef類,來達(dá)到線程安全的目的青瀑。
Run loop的管理并不完全是自動(dòng)的璧亮。
我們?nèi)员仨氃O(shè)計(jì)線程代碼以在適當(dāng)?shù)臅r(shí)候啟動(dòng)run loop并正確響應(yīng)輸入事件,當(dāng)然前提是線程中需要用到run loop斥难。而且枝嘶,我們還需要使用while/for語句來驅(qū)動(dòng)run loop能夠循環(huán)運(yùn)行,下面的代碼就成功驅(qū)動(dòng)了一個(gè)run loop:
1
2
3
4
BOOL isRunning = NO;
do {
isRunning = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDatedistantFuture]];
} while (isRunning);
Run loop同時(shí)也負(fù)責(zé)autorelease pool的創(chuàng)建和釋放哑诊。
在 使用手動(dòng)的內(nèi)存管理方式的項(xiàng)目中群扶,會(huì)經(jīng)常用到很多自動(dòng)釋放的對象,如果這些對象不能夠被即時(shí)釋放掉镀裤,會(huì)造成內(nèi)存占用量急劇增大竞阐。Run loop就為我們做了這樣的工作,每當(dāng)一個(gè)運(yùn)行循環(huán)結(jié)束的時(shí)候暑劝,它都會(huì)釋放一次autorelease pool骆莹,同時(shí)pool中的所有自動(dòng)釋放類型變量都會(huì)被釋放掉。
- ARC和MRC
Objective- c中提供了兩種內(nèi)存管理機(jī)制MRC(MannulReference Counting)和ARC(Automatic Reference Counting)担猛,分別提供對內(nèi)存的手動(dòng)和自動(dòng)管理幕垦,來滿足不同的需求。Xcode 4.1及其以前版本沒有ARC傅联。
在MRC的內(nèi)存管理 模式下智嚷,與對變量的管理相關(guān)的方法有:retain,release和autorelease。retain和release方法操作的是引用記數(shù)纺且,當(dāng)引 用記數(shù)為零時(shí)盏道,便自動(dòng)釋放內(nèi)存。并且可以用NSAutoreleasePool對象载碌,對加入自動(dòng)釋放池(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)識(shí)符感凤,可以分為變量標(biāo)識(shí)符和屬性標(biāo)識(shí)符,對于變量默認(rèn)為__strong粒督,而對于屬性默認(rèn)為unsafe_unretained陪竿。也存在autoreleasepool。
其 中assign/retain/copy與MRC下property的標(biāo)識(shí)符意義相同屠橄,strong類似與retain,assign類似于 unsafe_unretained族跛,strong/weak/unsafe_unretained與ARC下變量標(biāo)識(shí)符意義相同,只是一個(gè)用于屬性的標(biāo) 識(shí)锐墙,一個(gè)用于變量的標(biāo)識(shí)(帶兩個(gè)下劃短線__)礁哄。所列出的其他的標(biāo)識(shí)符與MRC下意義相同。
- 線程和進(jìn)程
進(jìn) 程溪北,是并發(fā)執(zhí)行的程序在執(zhí)行過程中分配和管理資源的基本單位姐仅,是一個(gè)動(dòng)態(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í)行過程是線狀的伯复,盡管中間會(huì)發(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)骑晶。
- 平常常用的多線程處理方式及優(yōu)缺點(diǎn)
iOS有四種多線程編程的技術(shù),分別是:NSThread草慧,Cocoa NSOperation桶蛔,GCD(全稱:Grand Central Dispatch),pthread。
四種方式的優(yōu)缺點(diǎn)介紹:
1)NSThread優(yōu)點(diǎn):NSThread 比其他兩個(gè)輕量級漫谷。缺點(diǎn):需要自己管理線程的生命周期仔雷,線程同步。線程同步對數(shù)據(jù)的加鎖會(huì)有一定的系統(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ù)蜘渣。
- pthread是一套通用的多線程API淌铐,適用于Linux\Windows\Unix,跨平臺(tái),可移植蔫缸,使用C語言腿准,生命周期需要程序員管理,IOS開發(fā)中使用很少捂龄。
GCD線程死鎖
GCD 確實(shí)好用 ,很強(qiáng)大加叁,相比NSOpretion 無法提供 取消任務(wù)的功能倦沧。
如此強(qiáng)大的工具用不好可能會(huì)出現(xiàn)線程死鎖。 如下代碼:
1
2
3
4
5
6
7
- (void)viewDidLoad{
[super viewDidLoad];
NSLog(@"=================4");
dispatch_sync(dispatch_get_main_queue(),
^{ NSLog(@"=================5"); });
NSLog(@"=================6");
}
GCD Queue 分為三種:
1它匕,The main queue :主隊(duì)列展融,主線程就是在個(gè)隊(duì)列中。
2豫柬,Global queues : 全局并發(fā)隊(duì)列告希。
3扑浸,用戶隊(duì)列:是用函數(shù) dispatch_queue_create創(chuàng)建的自定義隊(duì)列
dispatch_sync 和 dispatch_async 區(qū)別:
dispatch_async(queue,block) async 異步隊(duì)列,dispatch_async函數(shù)會(huì)立即返回, block會(huì)在后臺(tái)異步執(zhí)行燕偶。
dispatch_sync(queue,block) sync 同步隊(duì)列喝噪,dispatch_sync函數(shù)不會(huì)立即返回,及阻塞當(dāng)前線程,等待 block同步執(zhí)行完成指么。
分析上面代碼:
viewDidLoad 在主線程中酝惧, 及在dispatch_get_main_queue() 中,執(zhí)行到sync 時(shí) 向dispatch_get_main_queue()插入 同步 threed伯诬。sync 會(huì)等到 后面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ù)返回。下面例子:
1
2
3
4
5
6
7
8
- (void)viewDidLoad{
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"=================1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"=================2"); });
NSLog(@"=================3"); });
}
程序會(huì)完成執(zhí)行扔涧,為什么不會(huì)出現(xiàn)死鎖园担。
首先: async 在主線程中 創(chuàng)建了一個(gè)異步線程 加入 全局并發(fā)隊(duì)列,async 不會(huì)等待block 執(zhí)行完成枯夜,立即返回弯汰,
1,async 立即返回湖雹, viewDidLoad 執(zhí)行完畢咏闪,及主線程執(zhí)行完畢。
2摔吏, 同時(shí)鸽嫂,全局并發(fā)隊(duì)列立即執(zhí)行異步 block , 打印 1征讲, 當(dāng)執(zhí)行到 sync 它會(huì)等待 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í)行完成或 等了一會(huì),mainThread 很快退出筷狼, 2 等已執(zhí)行后繼續(xù)內(nèi)容瓶籽。如果阻塞了主線程,2 中的sync 就無法執(zhí)行啦埂材,mainThread 永遠(yuǎn)不會(huì)退出塑顺, sync 就永遠(yuǎn)等待著。
- 大量數(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)行全表掃描糙俗,如:
1
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值弧关,然后這樣查詢:
1
select id from t where num=0
3.應(yīng)盡量避免在 where 子句中使用 != 或 <> 操作符,否則將引擎放棄使用索引而進(jìn)行全表掃描嚣潜。
4.應(yīng)盡量避免在 where 子句中使用 or 來連接條件冬骚,如果一個(gè)字段有索引,一個(gè)字段沒有索引懂算,將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描只冻,如:
1
select id from t where num=10 or Name='admin'
可以這樣查詢:
1
select id from t where num=10 union all select id from t where Name='admin'
5.in 和 not in 也要慎用,否則會(huì)導(dǎo)致全表掃描计技,如:
1
select id from t where num in (1,2,3)
對于連續(xù)的數(shù)值喜德,能用 between 就不要用 in 了:
1
select id from t where num between 1 and 3
很多時(shí)候用 exists 代替 in 是一個(gè)好的選擇:
1
select num from a where num in (select num from b)
用下面的語句替換:
1
select num from a where exists (select 1 from b where num=a.num)
6.下面的查詢也將導(dǎo)致全表掃描:
1
select id from t where name like ‘%abc%’
若要提高效率,可以考慮全文檢索酸役。
- 如果在 where 子句中使用參數(shù)住诸,也會(huì)導(dǎo)致全表掃描。因?yàn)镾QL只有在運(yùn)行時(shí)才會(huì)解析局部變量涣澡,但優(yōu)化程序不能將訪問計(jì)劃的選擇推遲到運(yùn)行時(shí)贱呐;它必須在編譯時(shí)進(jìn)行選擇。然 而入桂,如果在編譯時(shí)建立訪問計(jì)劃奄薇,變量的值還是未知的,因而無法作為索引選擇的輸入項(xiàng)抗愁。如下面語句將進(jìn)行全表掃描:
1
select id from t where num=@num
可以改為強(qiáng)制查詢使用索引:
1
select id from t with (index(索引名)) where num=@num
應(yīng)盡量避免在 where 子句中對字段進(jìn)行表達(dá)式操作馁蒂,這將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描。如:
1
select id from t where num/2=100
應(yīng)改為:
1
select id from t where num=100*2
9.應(yīng)盡量避免在where子句中對字段進(jìn)行函數(shù)操作蜘腌,這將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描沫屡。如:
1
2
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)改為:
1
2
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)使用該索引勺届,否則該索引將不會(huì)被使用,并且應(yīng)盡可能的讓字段順序與索引順序相一致娶耍。
12.不要寫一些沒有意義的查詢免姿,如需要生成一個(gè)空表結(jié)構(gòu):
1
select col1,col2 into #t from t where1=0
這類代碼不會(huì)返回任何結(jié)果集,但是會(huì)消耗系統(tǒng)資源的榕酒,應(yīng)改成這樣:
1
create table #t(…)
13.Update 語句胚膊,如果只更改1、2個(gè)字段奈应,不要Update全部字段澜掩,否則頻繁調(diào)用會(huì)引起明顯的性能消耗,同時(shí)帶來大量日志杖挣。
14.對于多張大數(shù)據(jù)量(這里幾百條就算大了)的表JOIN肩榕,要先分頁再JOIN,否則邏輯讀會(huì)很高惩妇,性能很差株汉。
15.select count(*) from table;這樣不帶任何條件的count會(huì)引起全表掃描歌殃,并且沒有任何業(yè)務(wù)意義乔妈,是一定要杜絕的。
- 索引并不是越多越好氓皱,索引固然可以提高相應(yīng)的 select 的效率路召,但同時(shí)也降低了 insert 及 update 的效率勃刨,因?yàn)?insert 或 update 時(shí)有可能會(huì)重建索引,所以怎樣建索引需要慎重考慮股淡,視具體情況而定身隐。一個(gè)表的索引數(shù)最好不要超過6個(gè),若太多則應(yīng)考慮一些不常使用到的列上建的索引是否有 必要唯灵。
17.應(yīng)盡可能的避免更新 clustered 索引數(shù)據(jù)列贾铝,因?yàn)?clustered 索引數(shù)據(jù)列的順序就是表記錄的物理存儲(chǔ)順序,一旦該列值改變將導(dǎo)致整個(gè)表記錄的順序的調(diào)整埠帕,會(huì)耗費(fèi)相當(dāng)大的資源垢揩。若應(yīng)用系統(tǒng)需要頻繁更新 clustered 索引數(shù)據(jù)列,那么需要考慮是否應(yīng)將該索引建為 clustered 索引敛瓷。
18.盡量使用數(shù)字型字段叁巨,若只含數(shù)值信息的字段盡量不要設(shè)計(jì)為字符型,這會(huì)降低查詢和連接的性能呐籽,并會(huì)增加存儲(chǔ)開銷俘种。這是因?yàn)橐嬖谔幚聿樵兒瓦B 接時(shí)會(huì)逐個(gè)比較字符串中每一個(gè)字符,而對于數(shù)字型而言只需要比較一次就夠了绝淡。
19.盡可能的使用 varchar/nvarchar 代替 char/nchar 宙刘,因?yàn)槭紫茸冮L字段存儲(chǔ)空間小,可以節(jié)省存儲(chǔ)空間牢酵,其次對于查詢來說悬包,在一個(gè)相對較小的字段內(nèi)搜索效率顯然要高些。
20.任何地方都不要使用
1
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í)表,在存儲(chǔ)過程的最后務(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)先尋找基于集的解決方案來解決問題纺讲,基于集的方法通常更有效擂仍。
- 與臨時(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.在所有的存儲(chǔ)過程和觸發(fā)器的開始處設(shè)置 SET NOCOUNT ON 盲赊,在結(jié)束時(shí)設(shè)置 SET NOCOUNT OFF 。無需在執(zhí)行存儲(chǔ)過程和觸發(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è)操作是會(huì)鎖表的将硝,表一鎖住了恭朗,別的操作都進(jìn)不來了。
Apache 會(huì)有很多的子進(jìn)程或線程依疼。所以痰腮,其工作起來相當(dāng)有效率,而我們的服務(wù)器也不希望有太多的子進(jìn)程律罢,線程和數(shù)據(jù)庫鏈接膀值,這是極大的占服務(wù)器資源的事情,尤其是內(nèi)存误辑。
如果你把你的表鎖上一段時(shí)間沧踏,比如30秒鐘,那么對于一個(gè)有很高訪問量的站點(diǎn)來說巾钉,這30秒所積累的訪問進(jìn)程/線程翘狱,數(shù)據(jù)庫鏈接,打開的文件數(shù)砰苍,可能不僅僅會(huì)讓你的WEB服務(wù)崩潰潦匈,還可能會(huì)讓你的整臺(tái)服務(wù)器馬上掛了。
所以赚导,如果你有一個(gè)大的處理茬缩,你一定把其拆分,使用 LIMIT oracle(rownum),sqlserver(top)條件是一個(gè)好的方法吼旧。下面是一個(gè)mysql示例:
1
2
3
4
5
6
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)
}
- 常用到的動(dòng)畫庫
Facebook 開源動(dòng)畫庫 Pop 的 GitHub 主頁:facebook/pop · GitHub厂置,介紹:Playing with Pop (i)
Canvas 項(xiàng)目主頁:Canvas – Simplify iOS Development菩掏,介紹:Animate in Xcode Without Code
拿 Canvas 來和 Pop 比其實(shí)不大合適,雖然兩者都自稱「動(dòng)畫庫」昵济,但是「庫」這個(gè)詞的含義有所區(qū)別智绸。本質(zhì)上 Canvas 是一個(gè)「動(dòng)畫合集」而 Pop 是一個(gè)「動(dòng)畫引擎」。
先 說 Canvas访忿。Canvas 的目的是「Animate in Xcode Without Code」瞧栗。開發(fā)者可以通過在 Storyboard 中指定 User Defined Runtime Attributes 來實(shí)現(xiàn)一些 Canvas 中預(yù)設(shè)的動(dòng)畫,也就是他網(wǎng)站上能看到的那些海铆。但是除了更改動(dò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)動(dòng)畫」轉(zhuǎn)換成「動(dòng)態(tài)動(dòng)畫」竖幔。
同時(shí) Pop 又往前多走了一步。既然動(dòng)畫的本質(zhì)是根據(jù)時(shí)間函數(shù)來做插值是偷,那么理論上任何一個(gè)對象的任何一個(gè)值都可以用來做插值拳氢,而不僅僅是 Core Animation 里定死的那一堆大小、位移蛋铆、旋轉(zhuǎn)馋评、縮放等 animatable properties。
- 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)識(shí)(resource identifier)乳乌,對資源的操作不會(huì)改變這些標(biāo)識(shí)
3.所有的操作都是無狀態(tài)的
REST 簡化開發(fā)捧韵,其架構(gòu)遵循CRUD原則,該原則告訴我們對于資源(包括網(wǎng)絡(luò)資源)只需要四種行為:創(chuàng)建汉操,獲取再来,更新和刪除就可以完成相關(guān)的操作和處理。您可以 通過統(tǒng)一資源標(biāo)識(shí)符(Universal Resource Identifier磷瘤,URI)來識(shí)別和定位資源芒篷,并且針對這些資源而執(zhí)行的操作是通過 HTTP 規(guī)范定義的。其核心操作只有GET,PUT,POST,DELETE采缚。
由于REST強(qiáng)制所有的操作都必須是stateless的针炉,這就沒有上下文的約束,如果做分布式扳抽,集群都不需要考慮上下文和會(huì)話保持的問題篡帕。極大的提高系統(tǒng)的可伸縮性。
RESTful架構(gòu):
(1)每一個(gè)URI代表一種資源贸呢;
(2)客戶端和服務(wù)器之間镰烧,傳遞這種資源的某種表現(xiàn)層;
(3)客戶端通過四個(gè)HTTP動(dòng)詞楞陷,對服務(wù)器端資源進(jìn)行操作怔鳖,實(shí)現(xiàn)”表現(xiàn)層狀態(tài)轉(zhuǎn)化”。
- 請分析下SDWebImage的原理
這個(gè)類庫提供一個(gè)UIImageView類別以支持加載來自網(wǎng)絡(luò)的遠(yuǎn)程圖片固蛾。具有緩存管理败砂、異步下載赌渣、同一個(gè)URL下載次數(shù)控制和優(yōu)化等特征。
SDWebImage 加載圖片的流程:
1.入口 setImageWithURL:placeholderImage:options: 會(huì)先把 placeholderImage 顯示昌犹,然后 SDWebImageManager 根據(jù) URL 開始處理圖片坚芜。
- 進(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)存過小球榆,會(huì)先清空內(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 完成房待,不會(huì)拖慢主線程 UI邢羔。如果有需要對下載的圖片進(jìn)行二次處理,最好也在這里完成桑孩,效率會(huì)好很多拜鹤。
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í)候會(huì)注冊一些消息通知撑刺,在內(nèi)存警告或退到后臺(tái)的時(shí)候清理內(nèi)存圖片緩存,應(yīng)用結(jié)束的時(shí)候清理過期圖片握玛。
19.SDWI 也提供了 UIButton+WebCache 和 MKAnnotationView+WebCache够傍,方便使用。
20.SDWebImagePrefetcher 可以預(yù)先下載圖片挠铲,方便后續(xù)使用冕屯。
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)行存儲(chǔ)和讀染矫妗(實(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的形式存儲(chǔ)圖片。當(dāng)內(nèi)存不夠的時(shí)候會(huì)清除所有緩存圖 片。用搜索文件系統(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í)候丐吓,會(huì)有一個(gè)解壓操作浅悉,這樣效率 很低,但是只有瞬時(shí)的內(nèi)存需求券犁。為了提高效率通過SDWebImageDecoder將包裝在Data下的資源解壓术健,然后畫在另外一張圖片上,這樣這張新 圖片就不再需要重復(fù)解壓了粘衬。z做法是典型的空間換時(shí)間的做法荞估。
文章轉(zhuǎn)自吳白(簡書)