SDWebImage 不缓存特定 response header 的图片

问题场景

最近有这么一个场景,客户端使用 SDWebImage 请求一张用户头像图片,服务端若是发现客户端请求的用户头像图片 404 错误 (有可能该图片被删除了,反正就是不存在了) 那么服务端给 response 的 header 添加一个字段 ErrorCode,值为 100 ,并返回默认错误图片。这个时候客户端会显示服务端返回的默认错误图并缓存该图片。当服务端更新了原本 404 的用户头像图片之后并不会重新更新用户头像链接。不知道你有没有发现问题了 ? 客户端使用了 SDWebImage 缓存了图片之后,当需要再次使用该链接的图片的时候,客户端使用的 SDWebImage 并不会再次发起请求,而是使用缓存图片。这样的话,客户端会一直使用错误的用户头像。

解决方案

基于上面的使用场景,我需要让 SDWebImage 在下载图片的时候对 response 的 header 进行判断,在
header 中,若是存在一个 ErrorCode 的key并且 value 的值为 100。那么我就不缓存这张图片。这样客户端就不会缓存到错误的用户头像图片了。

实现思路

在对一个开源项目的做简单修改之前,我们需要对这个开源项目有个大概的了解,最好的了解渠道就是GitHub 主页,SDWebImage 的 GitHub 主页上有 SDWebImage 的架构图和流程图。

我们先看看流程图,流程图描述了 SDWebImage 的主要工作流程。


流程图

在流程图中,可以看到和图片下载请求相关的类是 SDWebImageDownloader 。


SDWebImageDownloader 流程图

我们看完了流程图,找到了 SDWebImageDownloader 这个负责图片下载的类,接下来一起看看架构图,看能否得到更多关于 SDWebImageDownloader 这个类的信息。


架构图

从架构图中可以看出,下载相关的操作确实是在 SDWebImageDownloader 里面,而且
SDWebImageDownloader 依赖 SDWebImageDownloaderOperation。初步判断,SDWebImageDownloaderOperation 是真正执行图片下载请求的地方。


SDWebImageDownloaderOperation

接下看就是看代码的时间了,先找到 SDWebImageDownloaderOperation 类所在位置


SDWebImageDownloaderOperation 位置

下一步,查看 SDWebImageDownloaderOperation.m 里面的下载相关的方法。从图中可以看到了一些 NSURLSessionDataDelegate 的方法,这个很明显是网络请求的代码了。


SDWebImageDownloaderOperation的方法

下一步,查看 SDWebImageDownloaderOperation 类的 NSURLSessionDataDelegate 实现方法 ,这个方法是用来接收网络请求响应的,所以我们在这里读取 response 的 header,判断服务端是否返回错误图片。

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler{
// 省略代码
}

把这个方法的代码通读一遍,想到了具体的实现方案,读取 response 的 header,若是存在一个 ErrorCode 的key并且 value 的值为 100,那么走原实现方法的 statusCode 大于 400 的判断分支。

//收到响应
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    
    //'304 Not Modified' is an exceptional one
    // (没有statusCode) 或者 (statusCode小于400 并且 statusCode 不等于304)
    // 若是请求响应成功,statusCode是200,那么会进入这个代码分支
    if (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304)) {
        //期望收到的数据量
        NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
        self.expectedSize = expected;
        //下载过程回调,收到数据量为0
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(0, expected, self.request.URL);
        }
        //根据期望收到的数据量创建NSMutableData,该NSMutableData用户保存收到的数据量
        self.imageData = [[NSMutableData alloc] initWithCapacity:expected];
        //保存响应对象
        self.response = response;
        //发送网络请求收到响应的通知
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadReceiveResponseNotification object:self];
        });
    }
    else {
        NSUInteger code = ((NSHTTPURLResponse *)response).statusCode;
        //This is the case when server returns '304 Not Modified'. It means that remote image is not changed.
        //In case of 304 we need just cancel the operation and return cached image from the cache.
        if (code == 304) {
            //code 304 取消下载 直接返回缓存中的内容
            [self cancelInternal];
        } else {
            //数据请求任务取消
            [self.dataTask cancel];
        }
        
        //发送停止下载的通知
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
        });
        // 错误处理回调
        [self callCompletionBlocksWithError:[NSError errorWithDomain:NSURLErrorDomain code:((NSHTTPURLResponse *)response).statusCode userInfo:nil]];

        [self done];
    }
    // 调用completionHandler回调
    if (completionHandler) {
        completionHandler(NSURLSessionResponseAllow);
    }
}

修改后的代码如下,修改的位置有 3 个

  1. 修改的地方 1,读取 header,判断是否是错误图片
  2. 修改的地方 2,增加一个 isErrorImage 的判断条件,控制代码执行条件
  3. 修改的地方 3,增加一个 isErrorImage 的判断条件,控制代码执行条件

修改完成之后,执行代码,验证成功。

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    
    // 修改的地方 1
    //是否是错误图片的标志位,用于控制代码 if 分支语句的执行
    BOOL isErrorImage = NO;
    //取 header 判断 ErrorCode 是否为 100,若为 100 则判定为错误图片
    NSDictionary *allHeaderFields =[(NSHTTPURLResponse *)response allHeaderFields];
    if ([allHeaderFields objectForKey:@"ErrorCode"]) {
        NSNumber *code = [allHeaderFields objectForKey:@"ErrorCode"];
        if ([code integerValue] == 100) {
            isErrorImage = YES;
        }
    }
    // 修改的地方 2
    if ( (!isErrorImage) && (![response respondsToSelector:@selector(statusCode)] || (((NSHTTPURLResponse *)response).statusCode < 400 && ((NSHTTPURLResponse *)response).statusCode != 304))) {
      // 省略代码
    }
    else {
        NSUInteger code = ((NSHTTPURLResponse *)response).statusCode;
        // 修改的地方 3
        if (code == 304 && (!isErrorImage)) {
            //code 304 取消下载 直接返回缓存中的内容
            [self cancelInternal];
        } else {
            //数据请求任务取消
            [self.dataTask cancel];
        }
    // 省略代码
    }
    // 省略代码

总结

对开源项目的修改通常都需要对整个项目有大致的理解,然后再根据自己的需求去寻找需要改动的点,最后进行验证测试。在这个过程中,对开源项目的理解或者了解越深,改动起来就越快!

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

推荐阅读更多精彩内容