SDWebImage图片下载实现分析

SDWebImage主要类的功能如下:

  • SDWebImageManager:负责查缓存,下载图片
  • SDImageCache:查缓存,分为内存缓存和磁盘缓存两步
  • SDWebImageDownloader:图片下载,保存回调block和构造下载operation并加入到queue
  • SDWebImageDownloaderOperation:实现图片下载及处理回调

SDWebImageDownloader的实现顺序是

  1. 保存回调block
  2. 构建下载operation
  3. 加入到queue后执行
  4. 开始下载
  5. 回调处理

这里主要通过SDWebImageDownloaderOperation分析下载和回调部分,参考代码是SDWebImage的最新版3.8.1

初始化

SDWebImageDownloader会创建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])) {
        //已设置:URL,缓存策略,超时时间,不使用默认cookie,管线化,HeaderFields
        _request = request;
        _shouldDecompressImages = YES;
        //选项:优先级,缓存,后台任务执行,cookie处理以及证书认证几个方面,在创建下载操作的时候可以使用组合的选项来完成一些特殊的需求
        _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被替换成了NSURLSessionNSURLSessionConfiguration以及NSURLSessionTask的3个子类:NSURLSessionDataTask,NSURLSessionUploadTask,NSURLSessionDownloadTask。

NSURLSession也是一组相互依赖的类,包括了NSURLRequestNSURLCache

一般一个请求创建过程为

  1. 配置会话属性NSURLSessionConfiguration,可以配置缓存,协议,cookie,以及证书策略(credential policy)
  2. 由一个NSURLSessionConfiguration对象来初始化一个NSURLSession对象
  3. 用NSURLSession对象创建NSURLSessionTask

简单说就是属性通过configuration配置,然后初始化session,最后用session创建task来执行。

  • configuration可以跨程序共享这些信息给每个session
  • session创建时可以指定委托回调对象
  • urlrequest在task创建时传入,调用resume方法开始执行请求操作

NSURLsessionTask 是一个抽象类,其下有 3 个实体子类可以直接使用:NSURLSessionDataTask、NSURLSessionUploadTask、NSURLSessionDownloadTask。这 3 个子类封装了现代程序三个最基本的网络任务:获取数据,比如 JSON 或者 XML,上传文件和下载文件。

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

start

在SDWebImageDownloader内,operation创建成功后被加入到downloadQueue中,轮到该operation执行时就会调用它的start方法

这里获取数据使用的是NSURLSessionDataTask

- (void)start {
    //保证只有当前线程执行该方法
    @synchronized (self) {
        //如果已经取消,清空数据
        if (self.isCancelled) {
            self.finished = YES;
            [self reset];
            return;
        }

        //进入后台时借用系统时间
#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和委托对象
        NSURLSession *session = self.unownedSession;
        if (!self.unownedSession) {
            //创建sessionConfig,配置会话属性
            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和委托对象,同SDWebImageDownloader生成的session类似
            self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
                                                              delegate:self
                                                         delegateQueue:nil];
            session = self.ownedSession;
        }
        
        //用session创建task,并传入requst
        self.dataTask = [session dataTaskWithRequest:self.request];
        self.executing = YES;
        self.thread = [NSThread currentThread];
    }
    
    //开始执行task
    [self.dataTask resume];

    if (self.dataTask) {
        //task创建成功,进度回调
        if (self.progressBlock) {
            self.progressBlock(0, NSURLResponseUnknownLength);
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            //通知开始下载,外部UI响应startActivity方法
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
        });
    }
    else {
        //task创建失败,完成回调
        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
    //停止在后台的执行
    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
}
委托方法

由于创建SDWebImageDownloaderOperation时传入了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;
        }
    }
    //返回对应下载的operation
    return returnOperation;
}

但downloader实现的NSURLSessionDataDelegateNSURLSessionTaskDelegate协议,都会转发给operation来执行。

  • NSURLSessionDelegate(继承NSObjectProtocol):处理session级别的任务,比如授权、后台session完成
  • NSURLSessionTaskDelegate(继承NSURLSessionDelegate:处理task级别的任务
  • NSURLSessionDataDelegate(继承NSURLSessionTaskDelegate:处理data和upload task的专属事件
  • NSURLSessionDownloadDelegate(继承NSURLSessionTaskDelegate:处理download task的专属事件,比如下载完成、百分比。
//接受到服务响应时调用的方法
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
//设置文件预期大小或取消
}

//接收到服务器返回的数据时调用的方法
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
//拼接数据
}

//基于请求缓存数据时调用
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler {
//缓存
}

在iOS7中,delegate引入了一种全新的处理方式。像这个缓存的回调方法,传入了block,可以让应用在中间进行一些操作之后来决定如何或是否继续执行后面的操作block。

//请求完成时调用的方法(成功或失败)
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
//成功block回调
}

//接收服务端挑战
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
//完成认证
}
cancel

取消operation分两种情况

- (void)cancel {
    @synchronized (self) {
        //请求完成时,会把thread置nil
        if (self.thread) {
            //请求已经完成
            [self performSelector:@selector(cancelInternalAndStop) onThread:self.thread withObject:nil waitUntilDone:NO];
        }
        else {
            //请求未完成,执行cancelBlock,cancel掉task,和一些数据清理工作
            [self cancelInternal];
        }
    }
}

以上就是SDWebImageDownloaderOperation主要代码的分析,开始和取消operation。主要是对session的一个运用,以及delegate的处理。

3.8.1是6月7号刚刚发布的版本,分析完这部分对图片的下载过程有个基本的了解,以后写代码多多借鉴。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,277评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,689评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,624评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,356评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,402评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,292评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,135评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,992评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,429评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,636评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,785评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,492评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,092评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,723评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,858评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,891评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,713评论 2 354

推荐阅读更多精彩内容