SDWebImage圖片下載實(shí)現(xiàn)分析

SDWebImage主要類的功能如下:

  • SDWebImageManager:負(fù)責(zé)查緩存逗抑,下載圖片
  • SDImageCache:查緩存弟胀,分為內(nèi)存緩存和磁盤緩存兩步
  • SDWebImageDownloader:圖片下載组哩,保存回調(diào)block和構(gòu)造下載operation并加入到queue
  • SDWebImageDownloaderOperation:實(shí)現(xiàn)圖片下載及處理回調(diào)

SDWebImageDownloader的實(shí)現(xiàn)順序是

  1. 保存回調(diào)block
  2. 構(gòu)建下載operation
  3. 加入到queue后執(zhí)行
  4. 開始下載
  5. 回調(diào)處理

這里主要通過SDWebImageDownloaderOperation分析下載和回調(diào)部分业筏,參考代碼是SDWebImage的最新版3.8.1

初始化

SDWebImageDownloader會(huì)創(chuàng)建SDWebImageDownloaderOperation,在com.hackemist.SDWebImageDownloaderBarrierQueue線程中趁啸。

- (id)initWithRequest:(NSURLRequest *)request
            inSession:(NSURLSession *)session
              options:(SDWebImageDownloaderOptions)options
             progress:(SDWebImageDownloaderProgressBlock)progressBlock
            completed:(SDWebImageDownloaderCompletedBlock)completedBlock
            cancelled:(SDWebImageNoParamsBlock)cancelBlock {
    if ((self = [super init])) {
        //已設(shè)置:URL舶斧,緩存策略欣鳖,超時(shí)時(shí)間,不使用默認(rèn)cookie茴厉,管線化泽台,HeaderFields
        _request = request;
        _shouldDecompressImages = YES;
        //選項(xiàng):優(yōu)先級(jí),緩存,后臺(tái)任務(wù)執(zhí)行,cookie處理以及證書認(rèn)證幾個(gè)方面,在創(chuàng)建下載操作的時(shí)候可以使用組合的選項(xiàng)來完成一些特殊的需求
        _options = options;
        //拷貝block 下載中,完成矾缓,取消
        _progressBlock = [progressBlock copy];
        _completedBlock = [completedBlock copy];
        _cancelBlock = [cancelBlock copy];
        _executing = NO;
        _finished = NO;
        _expectedSize = 0;
        //NSURLSession 7.0推出替代NSURLConnection
        _unownedSession = session;
        responseFromCached = YES; // Initially wrong until `- URLSession:dataTask:willCacheResponse:completionHandler: is called or not called
    }
    return self;
}

NSURLConnection被替換成了NSURLSession怀酷、NSURLSessionConfiguration以及NSURLSessionTask的3個(gè)子類:NSURLSessionDataTask,NSURLSessionUploadTask嗜闻,NSURLSessionDownloadTask蜕依。

NSURLSession也是一組相互依賴的類,包括了NSURLRequestNSURLCache琉雳。

一般一個(gè)請(qǐng)求創(chuàng)建過程為

  1. 配置會(huì)話屬性NSURLSessionConfiguration样眠,可以配置緩存,協(xié)議咐吼,cookie,以及證書策略(credential policy)
  2. 由一個(gè)NSURLSessionConfiguration對(duì)象來初始化一個(gè)NSURLSession對(duì)象
  3. 用NSURLSession對(duì)象創(chuàng)建NSURLSessionTask

簡單說就是屬性通過configuration配置商佑,然后初始化session锯茄,最后用session創(chuàng)建task來執(zhí)行。

  • configuration可以跨程序共享這些信息給每個(gè)session
  • session創(chuàng)建時(shí)可以指定委托回調(diào)對(duì)象
  • urlrequest在task創(chuàng)建時(shí)傳入茶没,調(diào)用resume方法開始執(zhí)行請(qǐng)求操作

NSURLsessionTask 是一個(gè)抽象類肌幽,其下有 3 個(gè)實(shí)體子類可以直接使用:NSURLSessionDataTask、NSURLSessionUploadTask抓半、NSURLSessionDownloadTask喂急。這 3 個(gè)子類封裝了現(xiàn)代程序三個(gè)最基本的網(wǎng)絡(luò)任務(wù):獲取數(shù)據(jù),比如 JSON 或者 XML笛求,上傳文件和下載文件廊移。

<img src="http://img.objccn.io/issue-5/NSURLSession.png" width = "400" />

start

在SDWebImageDownloader內(nèi),operation創(chuàng)建成功后被加入到downloadQueue中探入,輪到該operation執(zhí)行時(shí)就會(huì)調(diào)用它的start方法

這里獲取數(shù)據(jù)使用的是NSURLSessionDataTask

- (void)start {
    //保證只有當(dāng)前線程執(zhí)行該方法
    @synchronized (self) {
        //如果已經(jīng)取消狡孔,清空數(shù)據(jù)
        if (self.isCancelled) {
            self.finished = YES;
            [self reset];
            return;
        }

        //進(jìn)入后臺(tái)時(shí)借用系統(tǒng)時(shí)間
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
        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) {
                    [sself cancel];

                    [app endBackgroundTask:sself.backgroundTaskId];
                    sself.backgroundTaskId = UIBackgroundTaskInvalid;
                }
            }];
        }
#endif
        //在SDWebImageDownloader的init方法中初始化session,配置了config和委托對(duì)象
        NSURLSession *session = self.unownedSession;
        if (!self.unownedSession) {
            //創(chuàng)建sessionConfig蜂嗽,配置會(huì)話屬性
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;
            
            /**
             *  Create the session for this task
             *  We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate
             *  method calls and completion handler calls.
             */
            //初始化session苗膝,配置config和委托對(duì)象,同SDWebImageDownloader生成的session類似
            self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
                                                              delegate:self
                                                         delegateQueue:nil];
            session = self.ownedSession;
        }
        
        //用session創(chuàng)建task植旧,并傳入requst
        self.dataTask = [session dataTaskWithRequest:self.request];
        self.executing = YES;
        self.thread = [NSThread currentThread];
    }
    
    //開始執(zhí)行task
    [self.dataTask resume];

    if (self.dataTask) {
        //task創(chuàng)建成功辱揭,進(jìn)度回調(diào)
        if (self.progressBlock) {
            self.progressBlock(0, NSURLResponseUnknownLength);
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            //通知開始下載离唐,外部UI響應(yīng)startActivity方法
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
        });
    }
    else {
        //task創(chuàng)建失敗,完成回調(diào)
        if (self.completedBlock) {
            self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
        }
    }

#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
    //停止在后臺(tái)的執(zhí)行
    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
}
委托方法

由于創(chuàng)建SDWebImageDownloaderOperation時(shí)傳入了session问窃,session指定了他的持有者SDWebImageDownloader為delegate

- (SDWebImageDownloaderOperation *)operationWithTask:(NSURLSessionTask *)task {
    //通過委托方法傳入的task亥鬓,用taskId找到operation
    SDWebImageDownloaderOperation *returnOperation = nil;
    for (SDWebImageDownloaderOperation *operation in self.downloadQueue.operations) {
        if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
            returnOperation = operation;
            break;
        }
    }
    //返回對(duì)應(yīng)下載的operation
    return returnOperation;
}

但downloader實(shí)現(xiàn)的NSURLSessionDataDelegateNSURLSessionTaskDelegate協(xié)議,都會(huì)轉(zhuǎn)發(fā)給operation來執(zhí)行泡躯。

  • NSURLSessionDelegate(繼承NSObjectProtocol):處理session級(jí)別的任務(wù)贮竟,比如授權(quán)、后臺(tái)session完成
  • NSURLSessionTaskDelegate(繼承NSURLSessionDelegate:處理task級(jí)別的任務(wù)
  • NSURLSessionDataDelegate(繼承NSURLSessionTaskDelegate:處理data和upload task的專屬事件
  • NSURLSessionDownloadDelegate(繼承NSURLSessionTaskDelegate:處理download task的專屬事件较剃,比如下載完成咕别、百分比。
//接受到服務(wù)響應(yīng)時(shí)調(diào)用的方法
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
//設(shè)置文件預(yù)期大小或取消
}

//接收到服務(wù)器返回的數(shù)據(jù)時(shí)調(diào)用的方法
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
//拼接數(shù)據(jù)
}

//基于請(qǐng)求緩存數(shù)據(jù)時(shí)調(diào)用
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
//緩存
}

在iOS7中写穴,delegate引入了一種全新的處理方式惰拱。像這個(gè)緩存的回調(diào)方法,傳入了block啊送,可以讓應(yīng)用在中間進(jìn)行一些操作之后來決定如何或是否繼續(xù)執(zhí)行后面的操作block偿短。

//請(qǐng)求完成時(shí)調(diào)用的方法(成功或失敗)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
//成功block回調(diào)
}

//接收服務(wù)端挑戰(zhàn)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
//完成認(rèn)證
}
cancel

取消operation分兩種情況

- (void)cancel {
    @synchronized (self) {
        //請(qǐng)求完成時(shí)馋没,會(huì)把thread置nil
        if (self.thread) {
            //請(qǐng)求已經(jīng)完成
            [self performSelector:@selector(cancelInternalAndStop) onThread:self.thread withObject:nil waitUntilDone:NO];
        }
        else {
            //請(qǐng)求未完成昔逗,執(zhí)行cancelBlock,cancel掉task篷朵,和一些數(shù)據(jù)清理工作
            [self cancelInternal];
        }
    }
}

以上就是SDWebImageDownloaderOperation主要代碼的分析勾怒,開始和取消operation。主要是對(duì)session的一個(gè)運(yùn)用声旺,以及delegate的處理笔链。

3.8.1是6月7號(hào)剛剛發(fā)布的版本,分析完這部分對(duì)圖片的下載過程有個(gè)基本的了解腮猖,以后寫代碼多多借鑒鉴扫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市澈缺,隨后出現(xiàn)的幾起案子坪创,更是在濱河造成了極大的恐慌,老刑警劉巖姐赡,帶你破解...
    沈念sama閱讀 222,865評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件误堡,死亡現(xiàn)場離奇詭異,居然都是意外死亡雏吭,警方通過查閱死者的電腦和手機(jī)锁施,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人悉抵,你說我怎么就攤上這事肩狂。” “怎么了姥饰?”我有些...
    開封第一講書人閱讀 169,631評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵傻谁,是天一觀的道長。 經(jīng)常有香客問我列粪,道長审磁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,199評(píng)論 1 300
  • 正文 為了忘掉前任岂座,我火速辦了婚禮态蒂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘费什。我一直安慰自己钾恢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,196評(píng)論 6 398
  • 文/花漫 我一把揭開白布鸳址。 她就那樣靜靜地躺著瘩蚪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪稿黍。 梳的紋絲不亂的頭發(fā)上疹瘦,一...
    開封第一講書人閱讀 52,793評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音巡球,去河邊找鬼言沐。 笑死,一個(gè)胖子當(dāng)著我的面吹牛辕漂,可吹牛的內(nèi)容都是我干的呢灶。 我是一名探鬼主播吴超,決...
    沈念sama閱讀 41,221評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼钉嘹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鲸阻?” 一聲冷哼從身側(cè)響起跋涣,我...
    開封第一講書人閱讀 40,174評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鸟悴,沒想到半個(gè)月后陈辱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,699評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡细诸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,770評(píng)論 3 343
  • 正文 我和宋清朗相戀三年沛贪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,918評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡利赋,死狀恐怖水评,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情媚送,我是刑警寧澤中燥,帶...
    沈念sama閱讀 36,573評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站塘偎,受9級(jí)特大地震影響疗涉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吟秩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,255評(píng)論 3 336
  • 文/蒙蒙 一咱扣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧峰尝,春花似錦偏窝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至火窒,卻和暖如春硼补,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背熏矿。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評(píng)論 1 274
  • 我被黑心中介騙來泰國打工已骇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人票编。 一個(gè)月前我還...
    沈念sama閱讀 49,364評(píng)論 3 379
  • 正文 我出身青樓缔逛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親李命。 傳聞我的和親對(duì)象是個(gè)殘疾皇子片拍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,926評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容