讀懂「 唱吧 KTVHTTPCache 」設計思想

通過做者的指點,本文更新了日誌原理說明,HttpServer 做用,圖片緩存git

最近看到各大V轉發關於 唱吧音視頻框架 KTVHTTPCache 的開源消息,首先我很是感謝唱吧 iOS 團隊可以無私地把本身的成果開源。我本人對於緩存的設計也比較感興趣,也喜歡寫一些東西,但願能把本身一些小技巧分享給須要的同窗,這也是咱們 #iOS知識小集# 一直作的事情。抱着好奇的心,想了解一下唱吧是如何設計 KTVHTTPCache 的,沒想到越看越難,最後居然花了將近2天的時間看完了。程序員

安裝時解讀

在進行安裝的時候,發現 KTVHTTPCache 主要依賴了 CocoaHTTPServer 這個庫,而 CocoaHTTPServer 又依賴了 CocoaAsyncSocketCocoaLumberjack。能夠確定一點 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安全

podinstall.png

Readme 中提到:bash

KTVHTTPCache 由 HTTP Server 和 Data Storage 兩大模塊組成。服務器

另一個主要模塊就是 Data Storage,它主要負責資源加載及緩存處理。從這裏能夠看出,KTVHTTPCache 主要的工做量是設計 Data Storage 這個模塊,也就是它的核心所在。網絡

使用

其本質是對 HTTP 請求進行緩存,對傳輸內容並無限制,所以應用場景不限於音視頻在線播放,也能夠用於文件下載、圖片加載、普通網絡請求等場景。 --- KTVHTTPCacheapp

既然這麼好使,咱們能夠試試各類狀況,demo 中雖然沒有給出其它方式的緩存示例,咱們能夠探索一下。不過我測試了下載圖片的,並無成功,其它中狀況也就沒有試驗。我猜想,若是想支持這幾種狀況,應該須要修改源碼(若是做者能看到,忘解答一下,不知道個人猜想是否正確)。框架

視頻緩存( Demo 中提供,親測能夠)

  • 全局啓動一次便可,主要用來啓動 HttpServer,不理解的話,你能夠把它想成手機端的HTTP服務器,當你向HTTP服務器發出 Request 後,服務器會給你一個 Response,後面咱們會特地分析一個 HttpServer。

[KTVHTTPCache proxyStart:&error];

  • 根據原 url 生成一個 proxy url(代理 Url),並使用代理 url 獲取數據,這樣 HttpServer 就會截獲此次請求。好比原 url 爲 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
複製代碼

看圖會更好理解:

proxyUrl.png

NSString * proxyURLString = [KTVHTTPCache proxyURLStringWithOriginalURLString:URLString];
複製代碼
  • 播放,注意這裏使用的是代理url,進行播放,而不是原url。 [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, audioapplication/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)。

KTVHTTPCache.jpeg

HttpServer

這層設計比較簡單,主要是用了 CocoaHTTPServer 來做爲本地的 HttpServer。HttpServer 說白了就是一個手機端的服務器,用來與用戶(做者說的 client)交互,用戶提出數據加載需求後,它會從不一樣的地方來獲取數據源,若是本地沒有會從網絡中下載數據。它主要的做用是 hook 播放器的網絡請求,進行數據的加載。它主要的類如圖:

aonaotu-download-1.png

  • KTVHCHTTPServer:是一個單例,用來管理 HttpServer 服務,負責開啓或關閉服務;
  • KTVHCHTTPConnection:它繼承於 HTTPConnection,表示一個鏈接,它主要爲 HttpServer 提供 Response。
  • KTVHCHTTPRequest:一個請求,也就是一個數據模型;
  • KTVHCHTTPResponse:一個 Response;
  • KTVHCHTTPResponsePing:主要用來 ping 時的 Response;
  • KTVHCHTTPURL:主要用來處理 URL,好比把原 Url 生成 proxy url;

其實 HttpServer 的關鍵點是在 KTVHCHTTPConnection 中下面這個方法,它是鏈接緩存模塊的一個橋樑。使用 KTVHCDataRequestKTVHCHTTPConnection 來生成 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;
}
複製代碼

connetction.png

DataStroage

主要用來緩存數據,加載數據,也就是提供數據給 HttpServer。上面代碼中關鍵的一句代碼 [KTVHCHTTPResponse responseWithConnection:self dataRequest:dataRequest],它會在這個方法的內部使用 KTVHCDataStorage 生成一個 KTVHCDataReader,負責讀取數據。生成 KTVHCDataReader 後經過 [self.reader prepare] 來準備數據源 KTVHCDataSourcer,這裏主要有兩個數據源,KTVHCDataFileSourceKTVHCDataNetworkSource,它實現了協議 KTVHCDataSourceProtocolKTVHCDataNetworkSource 會經過 KTVHCDownload 下載數據。

須要說明一點,緩存是分片處理的

aonaotu-download.png

  • KTVHCDataStorage: 是一個單例,它負責管理整個緩存,好比讀取、保存和合並緩存;
  • KTVHCDataReader:主要用來讀取數據;
  • KTVHCDataRequest:用來請求數據,表示一個請求;
  • KTVHCDataResponse:一個數據響應;
  • KTVHCDataReader:讀取數據;
  • KTVHCDataCacheItem:緩存數據模型,表一個緩存項;
  • KTVHCDataCacheItemZone:緩存區,一個緩存項中會有多個緩存區,好比0-99,100-299 等;
  • KTVHCDataSourcer:數據源中心,負責處理不一樣數據源,它包含有一個數據隊列 KTVHCDataSourceQueue;
  • KTVHCDataSourceQueue:數據隊列;
  • KTVHCDataSourceProtocol:一個協議,做爲數據源時須要實現這個協議;
  • KTVHCDataFileSource:本地數據源,實現了 KTVHCDataSourceProtocol 協議;
  • KTVHCDataNetworkSource:網絡數據源,實現了 KTVHCDataSourceProtocol 協議;
  • KTVHCDataUnit:數據單元,至關於一個緩存目錄,好比一個視頻的緩存;
  • KTVHCDataUnitItem:數據單元項,緩存目錄下不一樣片斷的緩存;
  • KTVHCDataUnitPool:數據單元池,它是一個單例,含有一個 KTVHCDataUnitQueue;
  • KTVHCDataUnitQueue:數據單元隊列,保存了多個 KTVHCDataUnit,它會以 archive 的方式緩存到本地;

dataUnit.png

緩存目錄構成

結構

05f68836443a1535b73bfcf3c2e86d99 這個是由請求的原 url,md5 後生成的字符串,其中它的子目錄下會有多個文件,命名規則爲:urlmd5_offset_數字。文件名最後一位數字由於

Data Storeage 同時支持並行和串行。在並行場景中極端狀況可能遇到剛好同時存在兩個相同 Offset 的 Network Source,用來保證並行加載的安全性(實際場景中也沒遇到過,但在結構設計時把這部分考慮進去了)-- @程序員Single

  • 05f68836443a1535b73bfcf3c2e86d99
  • 05f68836443a1535b73bfcf3c2e86d99_0_0
  • 05f68836443a1535b73bfcf3c2e86d99_196608_0
  • 05f68836443a1535b73bfcf3c2e86d99_738108_0

沙盒目錄:

cache.png

緩存策略

例如一次請求的 Range 爲 0-999,本地緩存中已有 200-499 和 700-799 兩段數據。那麼會對應生成 5 個 Source,分別是:

  • 網絡: 0-199
  • 本地: 200-499
  • 網絡: 500-699
  • 本地: 700-799
  • 網絡: 800-999

日誌系統

作音視頻項目時,一個好的 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);                                                          \
}                                                                               \
}
複製代碼

總結

學習這個庫整體來講比較耗時,可是能學到做者的思想,這裏總結一下:

  • 職責明確,每一個類的做用定義明確;
  • KTVHCDataFileSourceKTVHCDataNetworkSource,使用協議 KTVHCDataSourceProtocol 的方式實現不一樣的 Source,而不用繼承,耦合性更低;
  • 使用簡單,內部定義複雜,緩緩相扣;
  • 使用 NSLock 保證線程安全;
  • 日誌定義周全,調試更容易;

歡迎關注我微博 Lefe_x,我會不按期的分享一些開發技巧

相關文章
相關標籤/搜索