通過做者的指點,本文更新了日誌原理說明,HttpServer 做用,圖片緩存git
最近看到各大V轉發關於 唱吧音視頻框架 KTVHTTPCache 的開源消息,首先我很是感謝唱吧 iOS 團隊可以無私地把本身的成果開源。我本人對於緩存的設計也比較感興趣,也喜歡寫一些東西,但願能把本身一些小技巧分享給須要的同窗,這也是咱們 #iOS知識小集# 一直作的事情。抱着好奇的心,想了解一下唱吧是如何設計 KTVHTTPCache 的,沒想到越看越難,最後居然花了將近2天的時間看完了。程序員
在進行安裝的時候,發現 KTVHTTPCache 主要依賴了 CocoaHTTPServer 這個庫,而 CocoaHTTPServer 又依賴了 CocoaAsyncSocket 和 CocoaLumberjack。能夠確定一點 KTVHTTPCache
使用 CocoaHTTPServer 做爲 HttpServer。github
CocoaHTTPServer is a small, lightweight, embeddable HTTP server for Mac OS X or iOS applications.緩存
Sometimes developers need an embedded HTTP server in their app. Perhaps it's a server application with remote monitoring. Or perhaps it's a desktop application using HTTP for the communication backend. Or perhaps it's an iOS app providing over-the-air access to documents. Whatever your reason, CocoaHTTPServer can get the job done安全
Readme 中提到:bash
KTVHTTPCache 由 HTTP Server 和 Data Storage 兩大模塊組成。服務器
另一個主要模塊就是 Data Storage
,它主要負責資源加載及緩存處理。從這裏能夠看出,KTVHTTPCache 主要的工做量是設計 Data Storage 這個模塊,也就是它的核心所在。網絡
其本質是對 HTTP 請求進行緩存,對傳輸內容並無限制,所以應用場景不限於音視頻在線播放,也能夠用於文件下載、圖片加載、普通網絡請求等場景。 --- KTVHTTPCacheapp
既然這麼好使,咱們能夠試試各類狀況,demo 中雖然沒有給出其它方式的緩存示例,咱們能夠探索一下。不過我測試了下載圖片的,並無成功,其它中狀況也就沒有試驗。我猜想,若是想支持這幾種狀況,應該須要修改源碼(若是做者能看到,忘解答一下,不知道個人猜想是否正確)。框架
[KTVHTTPCache proxyStart:&error];
http://lzaiuw.changba.com/userdata/video/940071102.mp4
它對應的 proxy url 爲http://localhost:53112/request-940071102.mp4?requestType=content&originalURL
=http%3A%2F%2Flzaiuw.changba.com%2Fuserdata%2Fvideo%2F940071102.mp4
複製代碼
看圖會更好理解:
NSString * proxyURLString = [KTVHTTPCache proxyURLStringWithOriginalURLString:URLString];
複製代碼
[AVPlayer playerWithURL:[NSURL URLWithString: proxyURLString]];
此次試驗結果沒能成功,在緩存中找不到緩存圖片,或許是我少些了什麼。
- (void)testImageCache
{
NSString *imageUrl = @"http://g.hiphotos.baidu.com/image/pic/item/e824b899a9014c08d8614343007b02087af4f4fa.jpg";
NSString *proxyStr = [KTVHTTPCache proxyURLStringWithOriginalURLString:imageUrl];
NSURLSessionTask *task2 = [[NSURLSession sharedSession] downloadTaskWithURL:[NSURL URLWithString:proxyStr]];
[task2 resume];
}
複製代碼
控制檯打印出的日誌能夠發現,圖片沒能緩存是由於 content type
錯誤致使的,由於 KTVHTTPCache 目前只支持 video
, audio
和 application/octet-stream
三種類型的,若是想緩存圖片能夠添加一種 content type。
KTVHTTPCache[818:16793] KTVHCDataNetworkSource : response error
http://g.hiphotos.baidu.com/image/pic/item/e824b899a9014c08d8614343007b02087af4f4fa.jpg
content type error
複製代碼
在 KTVHCDataRequest
類中的初始化方法中修改
self.acceptContentTypes = @[KTVHCDataContentTypeVideo,
KTVHCDataContentTypeAudio,
KTVHCDataContentTypeOctetStream];
複製代碼
爲
self.acceptContentTypes = @[KTVHCDataContentTypeVideo,
KTVHCDataContentTypeAudio,
KTVHCDataContentTypeOctetStream,
KTVHCDataContentTypeImage];
複製代碼
這樣既能夠支持緩存圖片的需求。這裏做者 @程序員Single 特別提示,若是作圖片緩存,不建議使用 HttpServer 作中轉,也就不須要 Proxy URL,這樣會節省沒必要要的開銷。HttpServer 的主要目的是爲了 Hook 播放器的網絡請求,從而能夠作到緩存數據。能夠直接使用 KTVHTTPCache
中的方法來生成 Reader 讀取數據。能夠參考 KTVHCHTTPResponse
的實現。
+ (KTVHCDataReader *)cacheConcurrentReaderWithRequest:(KTVHCDataRequest *)request;
+ (KTVHCDataReader *)cacheSerialReaderWithRequest:(KTVHCDataRequest *)request;
複製代碼
KTVHTTPCache 由 HTTP Server 和 Data Storage 兩大模塊組成。前者負責與 Client 交互,後者負責資源加載及緩存處理。
這句話若是沒有看源碼,其實很難理解,涉及到如何交互的問題(我是這樣認爲的,也許你比我聰明,能理解做者的含義)。通俗地講,HTTP Server 和 Data Storage 是 KTVHTTPCache 兩大重要組成部分, HTTP Server 主要負責與用戶交互,也就是最頂層,最直接與用戶交互(好比下載數據),而 Data Storage 則在後面爲 HTTP Server 提供數據,數據主要從 DataSourcer 中獲取,若是本地有數據,它會從 KTVHCDataFileSource 中獲取,反之會從 KTVHCDataNetworkSource 中讀取數據,這裏會走下載邏輯(KTVHCDownload)。
這層設計比較簡單,主要是用了 CocoaHTTPServer 來做爲本地的 HttpServer。HttpServer 說白了就是一個手機端的服務器,用來與用戶(做者說的 client)交互,用戶提出數據加載需求後,它會從不一樣的地方來獲取數據源,若是本地沒有會從網絡中下載數據。它主要的做用是 hook 播放器的網絡請求,進行數據的加載。它主要的類如圖:
其實 HttpServer 的關鍵點是在 KTVHCHTTPConnection 中下面這個方法,它是鏈接緩存模塊的一個橋樑。使用 KTVHCDataRequest
和 KTVHCHTTPConnection
來生成 KTVHCHTTPResponse
,關鍵點在於生成這個 Response。 這段代碼僅僅爲了說明問題,有刪減:
- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
{
KTVHCHTTPURL * URL = [KTVHCHTTPURL URLWithServerURIString:path];
switch (URL.type)
{
case KTVHCHTTPURLTypePing:
{
return [KTVHCHTTPResponsePing responseWithConnection:self];
}
case KTVHCHTTPURLTypeContent:
{
KTVHCHTTPRequest * currentRequest = [KTVHCHTTPRequest requestWithOriginalURLString:URL.originalURLString];
KTVHCDataRequest * dataRequest = [currentRequest dataRequest];
KTVHCHTTPResponse * currentResponse = [KTVHCHTTPResponse responseWithConnection:self dataRequest:dataRequest];
return currentResponse;
}
}
return nil;
}
複製代碼
主要用來緩存數據,加載數據,也就是提供數據給 HttpServer。上面代碼中關鍵的一句代碼 [KTVHCHTTPResponse responseWithConnection:self dataRequest:dataRequest]
,它會在這個方法的內部使用 KTVHCDataStorage
生成一個 KTVHCDataReader
,負責讀取數據。生成 KTVHCDataReader
後經過 [self.reader prepare]
來準備數據源 KTVHCDataSourcer
,這裏主要有兩個數據源,KTVHCDataFileSource
和 KTVHCDataNetworkSource
,它實現了協議 KTVHCDataSourceProtocol
。KTVHCDataNetworkSource
會經過 KTVHCDownload
下載數據。
須要說明一點,緩存是分片處理的
05f68836443a1535b73bfcf3c2e86d99
這個是由請求的原 url,md5 後生成的字符串,其中它的子目錄下會有多個文件,命名規則爲:urlmd5_offset_數字。文件名最後一位數字由於
Data Storeage 同時支持並行和串行。在並行場景中極端狀況可能遇到剛好同時存在兩個相同 Offset 的 Network Source,用來保證並行加載的安全性(實際場景中也沒遇到過,但在結構設計時把這部分考慮進去了)-- @程序員Single
沙盒目錄:
例如一次請求的 Range 爲 0-999,本地緩存中已有 200-499 和 700-799 兩段數據。那麼會對應生成 5 個 Source,分別是:
作音視頻項目時,一個好的 Log 管理能夠提升調試效率,而 KTVHTTPCache 能夠追蹤到每一次異常的請求。並且回記錄到一個 KTVHTTPCache.log
文件中。
KTVHTTPCache[818:16603] Proxy Start Success
KTVHTTPCache[818:16603] <KTVHCHTTPURL: 0x6040002349e0> : alloc
KTVHTTPCache[818:16603] KTVHCHTTPURL : Ping, original url, KTVHCHTTPURLPingResponseFile
KTVHTTPCache[818:16603] KTVHCHTTPURL : proxy url, http://localhost:49816/request-KTVHCHTTPURLPingResponseFile?requestType=ping&originalURL=KTVHCHTTPURLPingResponseFile
KTVHTTPCache[818:16603] <KTVHCHTTPURL: 0x6040002349e0> : dealloc
KTVHTTPCache[818:16795] <KTVHCHTTPConnection: 0x6000001331a0> : alloc
KTVHTTPCache[818:16793] KTVHCHTTPConnection : receive request, GET, /request-KTVHCHTTPURLPingResponseFile?requestType=ping&originalURL=KTVHCHTTPURLPingResponseFile
KTVHTTPCache[818:16793] <KTVHCHTTPURL: 0x604000238b00> : alloc
KTVHTTPCache[818:16793] KTVHCHTTPURL : Server URI, /request-KTVHCHTTPURLPingResponseFile?requestType=ping&originalURL=KTVHCHTTPURLPingResponseFile, original url, KTVHCHTTPURLPingResponseFile, type, 0
KTVHTTPCache[818:16793] <KTVHCHTTPResponsePing: 0x604000238b40> : alloc
KTVHTTPCache[818:16793] <KTVHCHTTPURL: 0x604000238b00> : dealloc
KTVHTTPCache[818:16793] KTVHCHTTPResponsePing : conetnt length, 4
KTVHTTPCache[818:16793] KTVHCHTTPResponsePing : read data length, 4, offset, 4 pang
KTVHTTPCache[818:16793] KTVHCHTTPResponsePing : check done, 1
KTVHTTPCache[818:16793] KTVHCHTTPResponsePing : connection did close, 4, 4
KTVHTTPCache[818:16793] <KTVHCHTTPResponsePing: 0x604000238b40> : dealloc
KTVHTTPCache[818:16603] KTVHCHTTPServer : ping result, 1
複製代碼
日誌的定義主要在類 KTVHCLog
中定義了一些宏。能夠經過下面的方法開啓日誌:
// 開啓控制檯打印
[KTVHTTPCache logSetConsoleLogEnable:YES];
// 開啓本地日誌記錄
[KTVHTTPCache logSetRecordLogEnable:YES];
複製代碼
特別說明一點能夠控制到每一個類是否打印:
KTVHCLogEnable(HTTPServer, YES, YES)
複製代碼
主要一個日誌的方法:
#define KTVHCLogging(target, console_log_enable, record_log_enable, ...) \
if ((console_log_enable) || (record_log_enable)) \
{ \
NSString * va_args = [NSString stringWithFormat:__VA_ARGS__]; \
NSString * log = [NSString stringWithFormat:@"%@ : %@", target, va_args]; \
if (record_log_enable) { \
NSLog(@"%@", log);; \
} \
if (console_log_enable) { \
NSLog(@"%@", log); \
} \
}
複製代碼
學習這個庫整體來講比較耗時,可是能學到做者的思想,這裏總結一下:
KTVHCDataFileSource
和 KTVHCDataNetworkSource
,使用協議 KTVHCDataSourceProtocol
的方式實現不一樣的 Source,而不用繼承,耦合性更低;