那些著名和非著名的 iOS 面試 - 前篇

如作為一個(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ò)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.如何追蹤app崩潰率,如何解決線上閃退 當(dāng) iOS設(shè)備上的App應(yīng)用閃退時(shí),操作系統(tǒng)會生成一個(gè)crash日志,...
    中婭沙漏閱讀 625評論 0 5
  • 1.如何追蹤app崩潰率,如何解決線上閃退 當(dāng)iOS設(shè)備上的App應(yīng)用閃退時(shí),操作系統(tǒng)會生成一個(gè)crash日志,保...
    戈多_于勒閱讀 407評論 0 0
  • 目錄 1.解釋self = [super init]方法 容錯(cuò)處理,當(dāng)父類初始化失敗,會返回一個(gè)nil,表示初始化...
    勇敢的_心_閱讀 1,553評論 0 4
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,663評論 1 32
  • 三色芋圓 三月伊始,春風(fēng)十里,桃之夭夭,不如約你~~ 今日與大家分享的一款人氣甜品--三色芋圓,健康甜品在家輕松制...
    摩瑪食堂閱讀 652評論 0 0

友情鏈接更多精彩內(nèi)容