
最近真的真的太太太忙了,都是抽空寫的,各種事情,html,iOS客戶端升級,炸了
上一篇我們基本上看完了SDWebImage整個工作流程,下面我們具體看一下緩存下載圖片中涉及到的相關(guān)的類
SDWebImageDownloader
SDWebImageManager實現(xiàn)下載依賴于下載器:SDWebImageDownloader,下載器負(fù)責(zé)管理下載任務(wù),而執(zhí)行下載任務(wù)是由SDWebImageDownloaderOperation操作完成
SDWebImageManager實現(xiàn)下載 就是調(diào)用下面這個方法:
- (id<SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock ```
我們還是先來看看`SDWebImageDownloader`里面都寫了些什么
**`SDWebImageDownloader.h`**
typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
//這個屬于默認(rèn)的使用模式了,前往下載,返回進度block信息,完成時調(diào)用completedBlock
SDWebImageDownloaderLowPriority = 1 << 0,
//漸進式下載 ,如果設(shè)置了這個選項,會在下載過程中,每次接收到一段返回數(shù)據(jù)就會調(diào)用一次完成回調(diào),回調(diào)中的image參數(shù)為未下載完成的部分圖像,可以實現(xiàn)將圖片一點點顯示出來的功能
SDWebImageDownloaderProgressiveDownload = 1 << 1,
/**
* 通常情況下request阻止使用NSURLCache.這個選項會默認(rèn)使用NSURLCache
*/
SDWebImageDownloaderUseNSURLCache = 1 << 2,
/**
* 如果從NSURLCache中讀取圖片,會在調(diào)用完成block的時候,傳遞空的image或者imageData
*/
SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
/**
* 系統(tǒng)為iOS 4+時候,如果應(yīng)用進入后臺,繼續(xù)下載.這個選項是為了實現(xiàn)在后臺申請額外的時間來完成請求.如果后臺任務(wù)到期,操作也會被取消
*/
SDWebImageDownloaderContinueInBackground = 1 << 4,
/**
* 通過設(shè)置 NSMutableURLRequest.HTTPShouldHandleCookies = YES的方式來處理存儲在NSHTTPCookieStore的cookies
*/
SDWebImageDownloaderHandleCookies = 1 << 5,
/**
* 允許不受信任的SSL證書,在測試環(huán)境中很有用,在生產(chǎn)環(huán)境中要謹(jǐn)慎使用
*/
SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,
/**
* 將圖片下載放到高優(yōu)先級隊列中
*/
SDWebImageDownloaderHighPriority = 1 << 7,
};
這些選項主要涉及到下載的優(yōu)先級,緩存,后臺任務(wù)執(zhí)行,cookie處理以及證書認(rèn)證幾個方面,在創(chuàng)建下載操作的時候可以使用組合的選項來完成一些特殊的需求
定義里兩個常量,后面通知的時候用的,這里的常量是全局常量
**全局常量**:不管你定義在任何文件夾,外部都能訪問
const NSString *myName = @"楊千嬅染了紅頭發(fā)";
**局部常量**:用**static**修飾后,不能提供外界訪問(只能在賦值的.m文件使用,外界不可訪問)
static const NSString *myName= @"楊千嬅染了紅頭發(fā)";
//官方也更推薦這樣定義常量 而不是用#define
extern NSString *const SDWebImageDownloadStartNotification;
extern NSString *const SDWebImageDownloadStopNotification;
定義了三個block
* 第一個返回已經(jīng)接收的圖片數(shù)據(jù)的大小,未接收的圖片數(shù)據(jù)的大小,`- (void)sd_setImageWithPreviousCachedImageWithURL: placeholderImage: options: progress:completed:`
這個方法里面就有用到,因為圖片的下載是需要時間的,所以這個block回調(diào)不止回調(diào)一次,會一直持續(xù)到圖片完全下載或者下載失敗才會停止回調(diào)
* 第二個block回調(diào) 下載完成的圖片 , 圖片的數(shù)據(jù) , 如果有error返回error ,以及下載是否完成的BOOl值
* 第三個是header過濾:設(shè)置一個過濾器,為下載圖片的HTTP request選取header.最終使用的headers是經(jīng)過這個block過濾時候的返回值
typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize);
typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage *image, NSData *data, NSError *error, BOOL finished);
typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDictionary *headers);
###定義的屬性
/**
- 解壓已經(jīng)下載緩存起來的圖片可以提高性能,但是會消耗大量的內(nèi)存
- 默認(rèn)為YES顯示比較高質(zhì)量的圖片,如果你遇到因內(nèi)存消耗過多而造成崩潰的話可以設(shè)置為NO,
*/
@property (assign, nonatomic) BOOL shouldDecompressImages;
//下載隊列最大的并發(fā)數(shù),意思是隊列中最多同時運行幾條線程(全局搜索了一下,默認(rèn)值是3)
@property (assign, nonatomic) NSInteger maxConcurrentDownloads;
/**
- 當(dāng)前在下載隊列的操作總數(shù),只讀(這是一個瞬間值,因為只要一個操作下載完成就會移除下載隊列)
*/
@property (readonly, nonatomic) NSUInteger currentDownloadCount;
/**
- 下載操作的超時時間,默認(rèn)是15s
*/
@property (assign, nonatomic) NSTimeInterval downloadTimeout;
/**
-
枚舉類型,代表著操作下載的順序
/
@property (assign, nonatomic) SDWebImageDownloaderExecutionOrder executionOrder;
//SDWebImageDownloaderExecutionOrder 的定義
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
/*- 默認(rèn)值,所有的下載操作以隊列類型(先進先出)執(zhí)行
*/
SDWebImageDownloaderFIFOExecutionOrder,
/**
- 所有的下載操作以棧類型(后進后出)執(zhí)行
/
SDWebImageDownloaderLIFOExecutionOrder
};
/*
- 默認(rèn)值,所有的下載操作以隊列類型(先進先出)執(zhí)行
SDWeImageDownloder是一個單例,這是初始化方法
*/
- (SDWebImageDownloader *)sharedDownloader;
/**
- 為request操作設(shè)置默認(rèn)的URL憑據(jù),具體實施為:在將操作添加到隊列之前,將操作的credential屬性值設(shè)置為urlCredential
*/
@property (strong, nonatomic) NSURLCredential *urlCredential;
/**
- Set username
*/
@property (strong, nonatomic) NSString *username;
/**
- Set password
/局部常量*
@property (strong, nonatomic) NSString *password;
/**
- 設(shè)置一個過濾器,為下載圖片的HTTP request選取header.意味著最終使用的headers是經(jīng)過這個block過濾之后的返回值。
*/
@property (nonatomic, copy) SDWebImageDownloaderHeadersFilterBlock headersFilter;
看完這些屬性后我們在來看**SDWebImageDownloader**里面的兩個核心方法,其他的方法會捎帶說一下
第一個就是一開始我們說的,**SDWebImageManager**會調(diào)用的方法
-
(id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
{
__block SDWebImageDownloaderOperation *operation;
__weak __typeof(self)wself = self;
[self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{//這里面都是創(chuàng)建下載的回調(diào)
}];
}
先來看看`-addProgressCallback:completedBlock:forURL:createCallback:`里面都做了些什么
- (void)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock forURL:(NSURL *)url createCallback:(SDWebImageNoParamsBlock)createCallback {
// The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
//如果圖片的url是空的就直接返回
if (url == nil) {
if (completedBlock != nil) {
completedBlock(nil, nil, nil, NO);
}
return;
}
dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new];
first = YES;
}
// Handle single download of simultaneous download request for the same URL
NSMutableArray *callbacksForURL = self.URLCallbacks[url];
NSMutableDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
[callbacksForURL addObject:callbacks];
self.URLCallbacks[url] = callbacksForURL;
if (first) {
createCallback();
}
});
}
下面重點也是不太好理解的東西,我也是又系統(tǒng)地復(fù)習(xí)了一下GCD,琢磨了有段時間才繼續(xù)寫的
dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new]; first = YES;
}
如果你GCD非常熟悉就跳過吧,不熟悉就先來看看我總結(jié)的GCD吧,寫的比較好理解,先來看看 幾個概念
**Serial 串行** ** Concurrent并發(fā) **
任務(wù)串行執(zhí)行每次只有一個任務(wù)執(zhí)行
任務(wù)并發(fā)執(zhí)行就是同一時間可以有多個任務(wù)被執(zhí)行
**Synchronous 同步**
一個同步函數(shù)只有在它完成預(yù)定的任務(wù)才返回(返回的意思是:返回當(dāng)前線程,線程繼續(xù)向下執(zhí)行任務(wù),你可以自己做個測試用一個同步函數(shù),任務(wù)里面sleep(3);測試一下就明白了)
**Asynchronous 異步**
一個異步函數(shù),會立即返回,預(yù)定任務(wù)會完成,但是不會等到這個任務(wù)完成才返回
**Queues 隊列**
GCD提供 dispatch queues來處理代碼,這些隊列管理你提供給GCD的任務(wù)并用FIFO順序執(zhí)行,這保證了第一個被添加到隊列里的任務(wù)會是隊列中第一個執(zhí)行的,第二個被添加的任務(wù)第二個開始執(zhí)行,如此直到隊列的終點
只能保證任務(wù)開始的順序不能保證任務(wù)結(jié)束的順序
**Serial Queues 串行隊列**
串行隊列的任務(wù)一次執(zhí)行一個,每一個任務(wù)只有在前一個任務(wù)完成的時候才開始,但是你不知道一個任務(wù)(block)和下一個開始之間的時間長度
**Concurrent Queues 并發(fā)隊列**
在并發(fā)隊列中的任務(wù)能得到的保證是它們會被按照被添加的順序開始執(zhí)行,任務(wù)能以任意順序完成,但是你不知道什么時候才開始運行下一個任務(wù),或者任意時刻有多少block在運行,這完全取決于GCD
**Queue Type 隊列類型**
主隊列(main queue),和其它串行隊列一樣,這個隊列中的任務(wù)一次只能執(zhí)行一個,然后它能保證所有的任務(wù)都在主線程執(zhí)行,而主線程是唯一可用于更新UI的線程,這個隊列就是用于發(fā)消息給UIView或發(fā)送通知的
全局調(diào)度隊列(Global Dispatch Queues),它分了四種優(yōu)先級(任務(wù)執(zhí)行的優(yōu)先級):background , low , default , high
Apple的API也會使用這些隊列,所以你添加的任何任務(wù)都不會是這些隊列唯一的任務(wù)
自己創(chuàng)建的串行隊列 或者并發(fā)隊列
**GCD提供的函數(shù)**
dispatch_async 異步 , 與其他線程無關(guān)
dispatch_sync 同步,阻塞其他線程
dispatch_apply 重復(fù)執(zhí)行
dispatch_after 延遲執(zhí)行
dispatch_barrier_async dispatch_barrier_sync(下面細(xì)講)
只列舉了一些常用的GCD函數(shù),并不完全
**GCD的使用呢,總結(jié)起來就是先選用一個GCD提供的函數(shù),傳入一個你要調(diào)用的隊列(三種隊列類型的一種)和一個block(任務(wù)),
隊列會在輪到這個block執(zhí)行的時候執(zhí)行這個block**
**注意:隊列是用來存放任務(wù)的,隊列并不等于線程,隊列中存放的任務(wù)最后都要由線程來執(zhí)行**
再回到剛才要看的部分,`dispatch_barrier_sync`是我們選用的GCD提供的函數(shù),`self.barrierQueue`是存放任務(wù)的隊列,block里面是要執(zhí)行的任務(wù)
dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new];
first = YES;
}
先來看看``dispatch_barrier_sync``
**Dispatch Barrier解決多線程并發(fā)讀寫一個資源發(fā)生死鎖**
sync說明了這是個同步函數(shù),任務(wù)不會立即返回,會等到任務(wù)執(zhí)行結(jié)束才返回
使用``dispatch_barrier_sync``此函數(shù)創(chuàng)建的任務(wù)會首先去查看隊列中有沒有別的任務(wù)要執(zhí)行,如果有則會等待已有任務(wù)執(zhí)行完畢再執(zhí)行;同時在此方法后添加的任務(wù)必須等到此方法中任務(wù)執(zhí)行后才能執(zhí)行,利用這個方法可以控制執(zhí)行順序
``Dispatch Barrier``確保提交的block是指定隊列中特定時段唯一在執(zhí)行的一個.在所有先于Dispatch Barrier的任務(wù)都完成的情況下這個block才開始執(zhí)行.輪到這個block時barrier會執(zhí)行這個block并且確保隊列在此過程 不會執(zhí)行其他任務(wù).block完成后才恢復(fù)隊列
_barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue",DISPATCH_QUEUE_CONCURRENT);
這是用戶自己創(chuàng)建的隊列,DISPATCH_QUEUE_CONCURRENT代表的是它是一個并行隊列,為什么選擇并發(fā)隊列而不是串行隊列我們來想一下:
串行隊列可以保證任務(wù)按照添加的順序一個個開始執(zhí)行,并且上一個任務(wù)結(jié)束才開始下一個任務(wù),這已經(jīng)可以保證任務(wù)的執(zhí)行順序(或者說是任務(wù)結(jié)束的順利)了,但是并行隊列不一樣,并發(fā)隊列只能保證任務(wù)的開始,至于任務(wù)以什么樣的順序結(jié)束并不能保證但是并發(fā)隊列使用``Barrier``卻是可以保證的
這部分就先到這里繼續(xù)向下看:
dispatch_barrier_sync(self.barrierQueue, ^{
BOOL first = NO;
if (!self.URLCallbacks[url]) {
self.URLCallbacks[url] = [NSMutableArray new];
first = YES;
}
NSMutableArray *callbacksForURL = self.URLCallbacks[url];
NSMutableDictionary *callbacks = [NSMutableDictionary new];
if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
[callbacksForURL addObject:callbacks];
self.URLCallbacks[url] = callbacksForURL;
URLCallbacks是一個可變字典,key是NSURL類型,value為NSMutableArray類型,value(數(shù)組里面)只包含一個元素,這個元素的類型是NSMutableDictionary類型,這個字典的key為NSString類型代表著回調(diào)類型,value為block,是對應(yīng)的回調(diào)
這些代碼的目的都是為了給url綁定回調(diào)
繼續(xù)向下看:
if (first) {
createCallback();
}
如果url第一次綁定它的回調(diào),也就是第一次使用這個url創(chuàng)建下載任務(wù)則執(zhí)行一次創(chuàng)建回調(diào)
在創(chuàng)建回調(diào)中 創(chuàng)建下載操作(下載操作并不是在這里創(chuàng)建的),``dispatch_barrier_sync``執(zhí)行確保同一時間只有一個線程操作URLCallbacks屬性,也就是確保了下面創(chuàng)建過程中在給operation傳遞回調(diào)的時候能取到正確的self.URLCallbacks[url]值,同事確保后面有相同的url再次創(chuàng)建的時候``if (!self.URLCallbacks[url])``分支不再進入,first==NO,也就不再繼續(xù)調(diào)用創(chuàng)建回調(diào),這樣就確保了同一個url對應(yīng)的圖片不會重復(fù)下載
以上這部分代碼總結(jié)起來只做了一件事情:在barrierQueue隊列中創(chuàng)建下載任務(wù)
至此下載的任務(wù)都創(chuàng)建好了,下面該輪到下載的操作了:
-
(id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock {__block SDWebImageDownloaderOperation *operation; [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{ //創(chuàng)建下載的回調(diào),我們開始來看看創(chuàng)建完下載的回調(diào)之后里面都寫了什么事情 //配置下載超時的時間 NSTimeInterval timeoutInterval = wself.downloadTimeout; if (timeoutInterval == 0.0) { timeoutInterval = 15.0; }/**
創(chuàng)建請求對象,并根據(jù)options參數(shù)設(shè)置其屬性
為了避免潛在的重復(fù)緩存(NSURLCache + SDImageCache),
如果沒有明確告知需要緩存,
則禁用圖片請求的緩存操作, 這樣就只有SDImageCache進行了緩存
這里的options 是SDWebImageDownloaderOptions
*/
NSMutableURLRequest *request =
[[NSMutableURLRequest alloc] initWithURL:url
cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData)
timeoutInterval:timeoutInterval];
// 通過設(shè)置 NSMutableURLRequest.HTTPShouldHandleCookies = YES
//的方式來處理存儲在NSHTTPCookieStore的cookies
request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
//返回在接到上一個請求得得響應(yīng)之前,飾扣需要傳輸數(shù)據(jù),YES傳輸,NO不傳輸
request.HTTPShouldUsePipelining = YES;
}];
};
/**
如果你自定義了wself.headersFilter,那就用你自己設(shè)置的
wself.headersFilter來設(shè)置HTTP的header field
它的定義是
typedef NSDictionary *(^SDWebImageDownloaderHeadersFilterBlock)(NSURL *url, NSDictionary *headers);
一個返回結(jié)果為NSDictionary類型的block
如果你沒有自己設(shè)置wself.headersFilter那么就用SDWebImage提供的HTTPHeaders
HTTPHeaders在#import "SDWebImageDownloader.h",init方法里面初始化,下載webp圖片需要的header不一樣
(WebP格式,[谷歌]開發(fā)的一種旨在加快圖片加載速度的圖片格式。圖片壓縮體積大約只有JPEG的2/3,并能節(jié)省大量的服務(wù)器帶寬資源和數(shù)據(jù)空間)
ifdef SD_WEBP
_HTTPHeaders = [@{@"Accept": @"image/webp,image/*;q=0.8"} mutableCopy];
else
_HTTPHeaders = [@{@"Accept": @"image/*;q=0.8"} mutableCopy];
endif
*/
if (wself.headersFilter) {
request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
}
else {
request.allHTTPHeaderFields = wself.HTTPHeaders;
}
/**
創(chuàng)建SDWebImageDownLoaderOperation操作對象(下載的操作就是在SDWebImageDownLoaderOperation類里面進行的)
傳入了進度回調(diào),完成回調(diào),取消回調(diào)
@property (assign, nonatomic) Class operationClass;
將Class作為屬性存儲,初始化具體Class,使用的時候調(diào)用具體class的方法
*/
operation = [[wself.operationClass alloc] initWithRequest:request
options:options
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
//progress block回調(diào)的操作 SDWebImageDownloader sself = wself;
if (!sself) return;
__block NSArray callbacksForURL;
/
URLCallbacks是一個字典,key是url,value是一個數(shù)組,
數(shù)組里面裝的是字典,key是NSString代表著回調(diào)類型,value為block是對應(yīng)的回調(diào)
確保提交的block是指定隊列中特定時段唯一在執(zhí)行的一個.
*/
dispatch_sync(sself.barrierQueue, ^{
//根據(jù)key取出裝了字典的數(shù)組
callbacksForURL = [sself.URLCallbacks[url] copy];
});
for (NSDictionary *callbacks in callbacksForURL) {
dispatch_async(dispatch_get_main_queue(), ^{
//根據(jù)kProgressCallbackKey這個key取出進度的操作
SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
//返回已經(jīng)接收的數(shù)據(jù)字節(jié),以及未接收的數(shù)據(jù)(預(yù)計字節(jié))
if (callback) callback(receivedSize, expectedSize);
});
}
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
//completed block 回調(diào)的操作
SDWebImageDownloader *sself = wself;
if (!sself) return;
//依舊是根據(jù)url這個key取出一個里面裝了字典的數(shù)組
__block NSArray *callbacksForURL;
dispatch_barrier_sync(sself.barrierQueue, ^{
callbacksForURL = [sself.URLCallbacks[url] copy];
if (finished) {
//如果這個任務(wù)已經(jīng)完成,就根據(jù)url這個key從URLCallbacks字典里面刪除
[sself.URLCallbacks removeObjectForKey:url];
}
});
for (NSDictionary *callbacks in callbacksForURL) {
//根據(jù)kCompletedCallbackKey這個key取出SDWebImageDownloaderCompletedBlock(完成的block)
SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
//回調(diào) 圖片 data error 是否完成的
if (callback) callback(image, data, error, finished);
}
}
cancelled:^{
//將url對應(yīng)的所有回調(diào)移除 SDWebImageDownloader *sself = wself;
if (!sself) return;
dispatch_barrier_async(sself.barrierQueue, ^{
[sself.URLCallbacks removeObjectForKey:url];
});
}];
//上面 是SDWebImageDownloaderOperation *operation的創(chuàng)建,從這里開始就都是對operation的配置
// 設(shè)置是否需要解壓
operation.shouldDecompressImages = wself.shouldDecompressImages;
/**
用戶認(rèn)證 NSURLCredential
當(dāng)連接客戶端與服務(wù)端進行數(shù)據(jù)傳輸?shù)臅r候,web服務(wù)器
收到客戶端請求時可能需要先驗證客戶端是否是正常用戶,再決定是否返回該接口的真實數(shù)據(jù)
iOS7.0之前使用的網(wǎng)絡(luò)框架是NSURLConnection,在 2013 的 WWDC 上,
蘋果推出了 NSURLConnection 的繼任者:NSURLSession
SDWebImage使用的是NSURLConnection,這兩種網(wǎng)絡(luò)框架的認(rèn)證調(diào)用的方法也是不一樣的,有興趣的可以去google一下這里只看下NSURLConnection的認(rèn)證(在這里寫看著有些吃力,移步到這個代碼框外面閱讀)
*/
if (wself.urlCredential) {
operation.credential = wself.urlCredential;
} else if (wself.username && wself.password) {
operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
}
//根據(jù)下載選項SDWebImageDownloaderHighPriority設(shè)置優(yōu)先級
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
operation.queuePriority = NSOperationQueuePriorityLow;
}
//將下載操作加到下載隊列中
[wself.downloadQueue addOperation:operation];
if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
/**
根據(jù)executionOrder設(shè)置操作的依賴關(guān)系
executionOrder代表著下載操作執(zhí)行的順序,它是一個枚舉
SD添加下載任務(wù)是同步的,而且都是在self.barrierQueue這個并行隊列中,
同步添加任務(wù)。這樣也保證了根據(jù)executionOrder設(shè)置依賴關(guān)是正確的。
換句話說如果創(chuàng)建下載任務(wù)不是使用dispatch_barrier_sync完成的,而是使用異步方法 ,雖然依次添加創(chuàng)建下載操作A、B、C的任務(wù),但實際創(chuàng)建順序可能為A、C、B,這樣當(dāng)executionOrder的值是SDWebImageDownloaderLIFOExecutionOrder,設(shè)置的操作依賴關(guān)系就變成了A依賴C,C依賴B
typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
// 默認(rèn)值,所有的下載操作以隊列類型執(zhí)行,先被加入下載隊列的操作先執(zhí)行
SDWebImageDownloaderFIFOExecutionOrder,
// 所有的下載操作以棧類型執(zhí)行,后進先出,后被加入下載隊列的操作先執(zhí)行
SDWebImageDownloaderLIFOExecutionOrder
};
*/
[wself.lastAddedOperation addDependency:operation];
wself.lastAddedOperation = operation;
}
}];
return operation;
}
** NSURLCredential 身份認(rèn)證 **
認(rèn)證過程
1.web服務(wù)器接收到來自客戶端的請求
2.web服務(wù)并不直接返回數(shù)據(jù),而是要求客戶端提供認(rèn)證信息,也就是說挑戰(zhàn)是服務(wù)端向客戶端發(fā)起的
2.1要求客戶端提供用戶名與密碼挑戰(zhàn) NSInternetPassword
2.2 要求客戶端提供客戶端證書 NSClientCertificate
2.3要求客戶端信任該服務(wù)器
3.客戶端回調(diào)執(zhí)行,接收到需要提供認(rèn)證信息,然后提供認(rèn)證信息,并再次發(fā)送給web服務(wù)
4.web服務(wù)驗證認(rèn)證信息
4.1認(rèn)證成功,將最終的數(shù)據(jù)結(jié)果發(fā)送給客戶端
4.2認(rèn)證失敗,錯誤此次請求,返回錯誤碼401
---
Web服務(wù)需要驗證客戶端網(wǎng)絡(luò)請求
NSURLConnectionDelegate 提供的接收挑戰(zhàn),SDWeImage使用的就是這個方案
-(void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;```
至此下載管理 SDWebImageDownloader到這里就算結(jié)束了,它的主要作用就是創(chuàng)建下載任務(wù),管理下載任務(wù)(取消,下載等狀態(tài)改變)這里的重點就是對self.barrierQueue的理解,最后我們來看看SDWebImageDownloaderOptions下載操作和下載過程的實現(xiàn)
SDWebImageDownloaderOptions
它的作用就是網(wǎng)絡(luò)請求的配置,進行網(wǎng)絡(luò)請求以及數(shù)據(jù)處理
依舊先來看看它公開聲明的屬性和方法
@interface SDWebImageDownloaderOperation : NSOperation <SDWebImageOperation>
/**
* 下載時用于網(wǎng)絡(luò)請求的request
*/
@property (strong, nonatomic, readonly) NSURLRequest *request;
/**
* 圖片下載完成是否需要解壓
*/
@property (assign, nonatomic) BOOL shouldDecompressImages;
/**
* :URLConnection是否需要咨詢憑據(jù)倉庫來對連接進行授權(quán),默認(rèn)YES
*/
@property (nonatomic, assign) BOOL shouldUseCredentialStorage;
/**
* web服務(wù)要求客戶端進行挑戰(zhàn),用NSURLConnectionDelegate提供的方法接收挑戰(zhàn),最終會生成一個挑戰(zhàn)憑證,也是NSURLCredential的實例 credential
*/
@property (nonatomic, strong) NSURLCredential *credential;
/**
*
SDWebImageDownloader.h里面定義的,一些下載相關(guān)的選項
*/
@property (assign, nonatomic, readonly) SDWebImageDownloaderOptions options;
/**
* 預(yù)期的文件大小
*/
@property (assign, nonatomic) NSInteger expectedSize;
/**
* connection對象進行網(wǎng)絡(luò)訪問,接收到的response
*/
@property (strong, nonatomic) NSURLResponse *response;
/**
*
用默認(rèn)的屬性值初始化一個SDWebImageDownloaderOperation對象
*/
- (id)initWithRequest:(NSURLRequest *)request
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock;
然后繼續(xù)看SDWebImageDownloaderOperation.h
初始化方法,這個就是初始化一個SDWebImageDownloaderOperation實例,沒什么看點
- (id)initWithRequest:(NSURLRequest *)request
options:(SDWebImageDownloaderOptions)options
progress:(SDWebImageDownloaderProgressBlock)progressBlock
completed:(SDWebImageDownloaderCompletedBlock)completedBlock
cancelled:(SDWebImageNoParamsBlock)cancelBlock {
if ((self = [super init])) {
_request = request;
_shouldDecompressImages = YES;
_shouldUseCredentialStorage = YES;
_options = options;
_progressBlock = [progressBlock copy];
_completedBlock = [completedBlock copy];
_cancelBlock = [cancelBlock copy];
_executing = NO;
_finished = NO;
_expectedSize = 0;
responseFromCached = YES; // Initially wrong until `connection:willCacheResponse:` is called or not called
}
return self;
}
但是下面這個方法- (void)start就是關(guān)鍵了,它是對NSOperation- (void)start的重寫,這個方法是執(zhí)行下載任務(wù)的核心代碼
- (void)start {
//先加一把線程鎖,保證執(zhí)行到這里的時候只有當(dāng)前線程在執(zhí)行下面的方法
@synchronized (self) {
//如果下載操作被取消了
if (self.isCancelled) {
self.finished = YES;
//把下載相關(guān)的屬性置為nil
[self reset];
return;
}
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
/**
App 進入后臺時,請求繼續(xù)執(zhí)行一段時間的方法,
使用UIApplication的beginBackgroundTaskWithExpirationHandler方法向系統(tǒng)借用一點時間,
繼續(xù)執(zhí)行下面的代碼來完成connection的創(chuàng)建和進行下載任務(wù)。
*/
Class UIApplicationClass = NSClassFromString(@"UIApplication");
BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {
__weak __typeof__ (self) wself = self;
UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];
self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (wself) sself = wself;
if (sself) {
/**在后臺任務(wù)執(zhí)行時間超過最大時間時,
也就是后臺任務(wù)過期執(zhí)行過期回調(diào)。
在回調(diào)主動將這個后臺任務(wù)結(jié)束。
*/
[sself cancel];
[app endBackgroundTask:sself.backgroundTaskId];
sself.backgroundTaskId = UIBackgroundTaskInvalid;
}
}];
}
#endif
// 下載任務(wù)執(zhí)行的狀態(tài),在執(zhí)行是YES,不在執(zhí)行時NO
self.executing = YES;
//創(chuàng)建用于下載的connection
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
//獲取當(dāng)前得得線程
self.thread = [NSThread currentThread];
}
//開始下載
[self.connection start];
//如果connection創(chuàng)建完成
if (self.connection) {
if (self.progressBlock) {
//任務(wù)開始立刻執(zhí)行一次進度的回調(diào)
self.progressBlock(0, NSURLResponseUnknownLength);
}
dispatch_async(dispatch_get_main_queue(), ^{
//發(fā)送開始下載的通知
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
});
/**
在 [self.connection start];有返回結(jié)果(正常完成,有錯誤都算是結(jié)果)之前,
代碼會一直阻塞在CFRunLoopRun()或者CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false) 這里,
也就是說 [self.connection start];之后下載就一直在進行中,一直到下載完成或者出錯了(這兩種情況都會調(diào)用CFRunLoopStop),這個阻塞才會解除
*/
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
}
else {
CFRunLoopRun();
}
//如果圖片被正常的下載完成
if (!self.isFinished) {
//取消下載請求連接
[self.connection cancel];
/**
NSURLConnectionDelegate代理方法
主動調(diào)用,并制造一個錯誤,這個方法一旦被調(diào)用
代理就不會再接收connection的消息,也就是不在調(diào)用其他的任何代理方法,connection徹底結(jié)束
*/
[self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];
}
}
else {
//如果connection創(chuàng)建失敗,這里直接執(zhí)行完成回調(diào),并傳遞一個connection沒有初始化的錯誤
if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
}
}
//執(zhí)行到這里說明下載操作已經(jīng)完成了(無論是成功還是錯誤),所以要停止在后臺的執(zhí)行,使用endBackgroundTask:
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
Class UIApplicationClass = NSClassFromString(@"UIApplication");
if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
return;
}
if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
[app endBackgroundTask:self.backgroundTaskId];
self.backgroundTaskId = UIBackgroundTaskInvalid;
}
#endif
}
最后,我們來看NSURLConnection (delegate)
1.connection: didReceiveResponse:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
//如果statusCode<400并且不等304
if (![response respondsToSelector:@selector(statusCode)] || ([((NSHTTPURLResponse *)response) statusCode] < 400 && [((NSHTTPURLResponse *)response) statusCode] != 304)) {
//設(shè)置文件的預(yù)期大小,如果response.expectedContentLength >0那么預(yù)期文件的大小就是response.expectedContentLength ,反之就是0
NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
self.expectedSize = expected;
//立即完成一次進度回調(diào)
if (self.progressBlock) {
self.progressBlock(0, expected);
}
//初始化屬性imageDate,用于拼接圖片 二進制數(shù)據(jù)
self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
self.response = response;
dispatch_async(dispatch_get_main_queue(), ^{
//異步的 向主隊隊列發(fā)送一個通知
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self];
});
}
else {
NSUInteger code = [((NSHTTPURLResponse *)response) statusCode];
/**
如果 statusCode == 304 就調(diào)用[self cancelInternal]方法 ,或者取消self.connection的連接
取消操作,發(fā)送操作停止的通知,執(zhí)行完成回調(diào),停止當(dāng)前的runloop,設(shè)置下載完成標(biāo)記為YES,正在執(zhí)行的為NO,將屬性置為空
*/
if (code == 304) {
[self cancelInternal];
} else {
[self.connection cancel];
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
});
if (self.completedBlock) {
self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES);
}
CFRunLoopStop(CFRunLoopGetCurrent());
[self done];
}
}
2.connection: didReceiveData拼接數(shù)據(jù)的協(xié)議
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.imageData appendData:data];
if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0 && self.completedBlock) {
// 根據(jù)self.imageData獲取已接收的數(shù)據(jù)的長度
const NSInteger totalSize = self.imageData.length;
/**
每次接收到數(shù)據(jù)時,都會用現(xiàn)有的數(shù)據(jù)創(chuàng)建一個CGImageSourceRef對象以做處理,
而且這個數(shù)據(jù)應(yīng)該是已接收的全部數(shù)據(jù),而不僅僅是新的字節(jié),所以才使用self.imageData作為參數(shù)(注意創(chuàng)建imageSource使用的數(shù)據(jù)是CoreFoundation的data,但是self.imageData是NSData,所以用(__bridge CFDataRef)self.imageData做轉(zhuǎn)化 )
*/
CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)self.imageData, NULL);
/**
在首次接收到數(shù)據(jù)的時候,圖片的長寬都是0(width+height == 0)
先從這些包含圖像信息的數(shù)據(jù)中取出圖像的長,寬,方向等信息以備使用
*/
if (width + height == 0) {
//獲取圖片的屬性信息
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
if (properties) {
NSInteger orientationValue = -1;
//圖片像素的高度 可以前面加(__bridge NSNumber *)轉(zhuǎn)換為NSNumber類型
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
if (val) CFNumberGetValue(val, kCFNumberLongType, &height);
//獲取圖片的寬度
val = CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
if (val) CFNumberGetValue(val, kCFNumberLongType, &width);
//獲取圖片的朝向
val = CFDictionaryGetValue(properties, kCGImagePropertyOrientation);
if (val) CFNumberGetValue(val, kCFNumberNSIntegerType, &orientationValue);
//CoreFoundation對象類型不在ARC范圍內(nèi),需要手動釋放資源
CFRelease(properties);
/**
使用Core Craphics框架繪制image時,使用的是
initWithCGImage這個函數(shù),但是使用這個函數(shù)有時候會造成圖片朝向的錯誤,
所以在這里保存朝向信息,orientation是一個可以記錄圖片方向的枚舉
*/
orientation = [[self class] orientationFromPropertyValue:(orientationValue == -1 ? 1 : orientationValue)];
}
}
/**
width+height>0 說明這時候已經(jīng)接收到圖片的數(shù)據(jù)了
totalSize < self.expectedSize 說明圖片 還沒有接收完全
*/
if (width + height > 0 && totalSize < self.expectedSize) {
// 創(chuàng)建圖片
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
#ifdef TARGET_OS_IPHONE
// Workaround for iOS anamorphic(失真的 , 變形的) image
if (partialImageRef) {
const size_t partialHeight = CGImageGetHeight(partialImageRef);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bmContext = CGBitmapContextCreate(NULL, width, height, 8, width * 4, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
if (bmContext) {
CGContextDrawImage(bmContext, (CGRect){.origin.x = 0.0f, .origin.y = 0.0f, .size.width = width, .size.height = partialHeight}, partialImageRef);
CGImageRelease(partialImageRef);
partialImageRef = CGBitmapContextCreateImage(bmContext);
CGContextRelease(bmContext);
}
else {
CGImageRelease(partialImageRef);
partialImageRef = nil;
}
}
#endif
if (partialImageRef) {
UIImage *image = [UIImage imageWithCGImage:partialImageRef scale:1 orientation:orientation];
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
UIImage *scaledImage = [self scaledImageForKey:key image:image];
if (self.shouldDecompressImages) {
image = [UIImage decodedImageWithImage:scaledImage];
}
else {
image = scaledImage;
}
CGImageRelease(partialImageRef);
dispatch_main_sync_safe(^{
if (self.completedBlock) {
self.completedBlock(image, nil, nil, NO);
}
});
}
}
CFRelease(imageSource);
}
if (self.progressBlock) {
self.progressBlock(self.imageData.length, self.expectedSize);
}
}
3.connectionDidFinishLoading:這個方法完成以后,代理不再會接收人和connection發(fā)送的消息,標(biāo)志著圖片下載完成,一般下載任務(wù)正常結(jié)束之后就會執(zhí)行一次這個方法
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
@synchronized(self) {
/**
停止當(dāng)前的runLoop,將connection屬性和thread屬性
發(fā)送下載停止的通知
*/
CFRunLoopStop(CFRunLoopGetCurrent());
self.thread = nil;
self.connection = nil;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
[[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
});
}
/**
檢查sharedURLCache是否緩存了這次下載response
如果沒有就把responseFromCached設(shè)置為NO
*/
if (![[NSURLCache sharedURLCache] cachedResponseForRequest:_request]) {
responseFromCached = NO;
}
/**
執(zhí)行完成回調(diào)
*/
if (completionBlock) {
/**
圖片的緩存用的都是SDWebCache,所以就算設(shè)置了SDWebImageDownloaderIgnoreCachedResponse,
responseFromCached
回調(diào)的圖片也是nil(理解有可能有偏差)
*/
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {
completionBlock(nil, nil, nil, YES);
} else if (self.imageData) {
//將數(shù)據(jù)轉(zhuǎn)換為UIImage類型
UIImage *image = [UIImage sd_imageWithData:self.imageData];
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
image = [self scaledImageForKey:key image:image];
// 注意對于gif圖片,不需要解壓縮
if (!image.images) {
if (self.shouldDecompressImages) {
image = [UIImage decodedImageWithImage:image];
}
}
if (CGSizeEqualToSize(image.size, CGSizeZero)) {
//如果圖片的大小為0 , 完成回調(diào)報錯
completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
}
else {
//回調(diào)圖片 已經(jīng)圖片的大小 完成狀態(tài)YES
completionBlock(image, self.imageData, nil, YES);
}
} else {
//圖片為空 回調(diào) 報錯
completionBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}], YES);
}
}
self.completionBlock = nil;
//將NSConnection 設(shè)置為完成狀態(tài)
[self done];
}
到這里,看的也差不多了,認(rèn)真看完感覺這個作者太厲害了,也真的學(xué)習(xí)到了很多,歡迎交流,也希望大家自己有空了也看一下,這次真的是拖了一個月因為有的東西我沒明白就看了好多天也查了 各種資料,這次也算是盡力寫好了吧,慚愧
包廂里的狂歡,曲終人散
have Fine
以上