2016-03-16 sdwebimage

2023-11-01 19:40
文章标签 16 03 2016 sdwebimage

本文主要是介绍2016-03-16 sdwebimage,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

SDWebImage框架底层讲解

一. 异步加载图片

1.搭建界面&数据准备

  • 数据准备
@interface AppInfo : NSObject
/// App 名称 @property (nonatomic, copy) NSString *name; /// 图标 URL @property (nonatomic, copy) NSString *icon; /// 下载数量 @property (nonatomic, copy) NSString *download; + (instancetype)appInfoWithDict:(NSDictionary *)dict; /// 从 Plist 加载 AppInfo + (NSArray *)appList; @end
+ (instancetype)appInfoWithDict:(NSDictionary *)dict {id obj = [[self alloc] init];[obj setValuesForKeysWithDictionary:dict];return obj;
}/// 从 Plist 加载 AppInfo + (NSArray *)appList { NSURL *url = [[NSBundle mainBundle] URLForResource:@"apps.plist" withExtension:nil]; NSArray *array = [NSArray arrayWithContentsOfURL:url]; NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:array.count]; [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { [arrayM addObject:[self appInfoWithDict:obj]]; }]; return arrayM.copy; }
  • 视图控制器数据
///  应用程序列表
@property (nonatomic, strong) NSArray *appList;
  • 懒加载
- (NSArray *)appList {if (_appList == nil) {_appList = [AppInfo appList];}return _appList;
}
  • 表格数据源方法
#pragma mark - 数据源方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {return self.appList.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppCell"]; // 设置 Cell... AppInfo *app = self.appList[indexPath.row]; cell.textLabel.text = app.name; cell.detailTextLabel.text = app.download; return cell; }
知识点
  1. 数据模型应该负责所有数据准备工作,在需要时被调用
  2. 数据模型不需要关心被谁调用
  3. 数组使用
    • [NSMutableArray arrayWithCapacity:array.count]; 的效率更高
    • 使用块代码遍历的效率比 for 要快
  4. @"AppCell" 格式定义的字符串是保存在常量区的
  5. 在 OC 中,懒加载是无处不在的
    • 设置 cell 内容时如果没有指定图像,则不会创建 imageView

2.同步加载图像

// 同步加载图像
// 1. 模拟延时
NSLog(@"正在下载 %@", app.name); [NSThread sleepForTimeInterval:0.5]; // 2. 同步加载网络图片 NSURL *url = [NSURL URLWithString:app.icon]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; cell.imageView.image = image;

注意:之前没有设置 imageView 时,imageView 并不会被创建

  • 存在的问题
    1. 如果网速慢,会卡爆了!影响用户体验
    2. 滚动表格,会重复下载图像,造成用户经济上的损失!

解决办法--->异步下载图像

3.异步下载图像

  • 全局操作队列
///  全局队列,统一管理所有下载操作
@property (nonatomic, strong) NSOperationQueue *downloadQueue;
  • 懒加载
- (NSOperationQueue *)downloadQueue {if (_downloadQueue == nil) {_downloadQueue = [[NSOperationQueue alloc] init];}return _downloadQueue; }
  • 异步下载
// 异步加载图像
// 1. 定义下载操作
// 异步加载图像
NSBlockOperation *downloadOp = [NSBlockOperation blockOperationWithBlock:^{ // 1. 模拟延时 NSLog(@"正在下载 %@", app.name); [NSThread sleepForTimeInterval:0.5]; // 2. 异步加载网络图片 NSURL *url = [NSURL URLWithString:app.icon]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; // 3. 主线程更新 UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ cell.imageView.image = image; }]; }]; // 2. 将下载操作添加到队列 [self.downloadQueue addOperation:downloadOp];

运行测试,存在的问题--->下载完成后不显示图片

原因分析:

  • 使用的是系统提供的 cell
  • 异步方法中只设置了图像,但是没有设置 frame
  • 图像加载后,一旦与 cell 交互,会调用 cell 的 layoutSubviews 方法,重新调整 cell 的布局

解决办法--->使用占位图像 or 自定义 Cell

注意演示不在主线程更新图像的效果

4.占位图像

// 占位图像
UIImage *placeholder = [UIImage imageNamed:@"user_default"];
cell.imageView.image = placeholder;
  • 问题
    1. 因为使用的是系统提供的 cell
    2. 每次和 cell 交互,layoutSubviews 方法会根据图像的大小自动调整 imageView 的尺寸

解决办法--->自定义 Cell

自定义 Cell
cell.nameLabel.text = app.name;
cell.downloadLabel.text = app.download; // 异步加载图像 // 0. 占位图像 UIImage *placeholder = [UIImage imageNamed:@"user_default"]; cell.iconView.image = placeholder; // 1. 定义下载操作 NSBlockOperation *downloadOp = [NSBlockOperation blockOperationWithBlock:^{ // 1. 模拟延时 NSLog(@"正在下载 %@", app.name); [NSThread sleepForTimeInterval:0.5]; // 2. 异步加载网络图片 NSURL *url = [NSURL URLWithString:app.icon]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; // 3. 主线程更新 UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ cell.iconView.image = image; }]; }]; // 2. 将下载操作添加到队列 [self.downloadQueue addOperation:downloadOp];
  • 问题

    1. 如果网络图片下载速度不一致,同时用户滚动图片,可能会出现图片显示"错行"的问题

    2. 修改延时代码,查看错误

// 1. 模拟延时
if (indexPath.row > 9) {[NSThread sleepForTimeInterval:3.0]; }

上下滚动一下表格即可看到 cell 复用的错误

解决办法---> MVC

5.MVC

  • 在模型中添加 image 属性
#import <UIKit/UIKit.h>///  下载的图像
@property (nonatomic, strong) UIImage *image;
使用 MVC 更新表格图像
  • 判断模型中是否已经存在图像

    if (app.image != nil) {NSLog(@"加载模型图像..."); cell.iconView.image = app.image; return cell; }
  • 下载完成后设置模型图像

// 3. 主线程更新 UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{// 设置模型中的图像app.image = image;// 刷新表格 [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; }];
  • 问题

    1. 如果图像下载很慢,用户滚动表格很快,会造成重复创建下载操作

    2. 修改延时代码

// 模拟延时
if (indexPath.row == 0) {[NSThread sleepForTimeInterval:10.0]; }

快速滚动表格,将第一行不断“滚出/滚入”界面可以查看操作被重复创建的问题

解决办法 ---> 操作缓冲池

6.操作缓冲池

所谓缓冲池,其实就是一个容器,能够存放多个对象

  • 数组:按照下标,可以通过 indexPath 可以判断操作是否已经在进行中
    • 无法解决上拉&下拉刷新
  • NSSet -> 无序的
    • 无法定位到缓存的操作
  • 字典:按照key,可以通过下载图像的 URL(唯一定位网络资源的字符串)

小结:选择字典作为操作缓冲池

缓冲池属性
///  操作缓冲池
@property (nonatomic, strong) NSMutableDictionary *operationCache;
  • 懒加载
- (NSMutableDictionary *)operationCache {if (_operationCache == nil) {_operationCache = [NSMutableDictionary dictionary];}return _operationCache; }
修改代码
  • 判断下载操作是否被缓存——正在下载
// 异步加载图像
// 0. 占位图像
UIImage *placeholder = [UIImage imageNamed:@"user_default"]; cell.iconView.image = placeholder; // 判断操作是否存在 if (self.operationCache[app.icon] != nil) { NSLog(@"正在玩命下载中..."); return cell; }
  • 将操作添加到操作缓冲池
// 2. 将操作添加到操作缓冲池
[self.operationCache setObject:downloadOp forKey:app.icon];// 3. 将下载操作添加到队列 [self.downloadQueue addOperation:downloadOp];

修改占位图像的代码位置,观察会出现的问题

  • 下载完成后,将操作从缓冲池中删除
[self.operationCache removeObjectForKey:app.icon];
循环引用分析!
  • 弱引用 self 的编写方法:
__weak typeof(self) weakSelf = self;
  • 利用 dealloc 辅助分析
- (void)dealloc {NSLog(@"我给你最后的疼爱是手放开");
}
  • 注意
    • 如果使用 self,视图控制器会在下载完成后被销毁
    • 而使用 weakSelf,视图控制器在第一时间被销毁

8.代码重构

重构目的
  • 相同的代码最好只出现一次
  • 主次方法
    • 主方法
      • 只包含实现完整逻辑的子方法
      • 思维清楚,便于阅读
    • 次方法
      • 实现具体逻辑功能
      • 测试通过后,后续几乎不用维护
重构的步骤
  • 1.新建一个方法
    • 新建方法
    • 把要抽取的代码,直接复制到新方法中
    • 根据需求调整参数
  • 2.调整旧代码
    • 注释原代码,给自己一个后悔的机会
    • 调用新方法
  • 3.测试
  • 4.优化代码
    • 在原有位置,因为要照顾更多的逻辑,代码有可能是合理的
    • 而抽取之后,因为代码少了,可以检查是否能够优化
    • 分支嵌套多,不仅执行性能会差,而且不易于阅读
  • 5.测试
  • 6.修改注释
    • 在开发中,注释不是越多越好
    • 如果忽视了注释,有可能过一段时间,自己都看不懂那个注释
    • .m 关键的实现逻辑,或者复杂代码,需要添加注释,否则,时间长了自己都看不懂!
    • .h 中的所有属性和方法,都需要有完整的注释,因为 .h 文件是给整个团队看的

重构一定要小步走,要边改变测试

重构后的代码
- (void)downloadImage:(NSIndexPath *)indexPath {// 1. 根据 indexPath 获取数据模型AppInfo *app = self.appList[indexPath.row]; // 2. 判断操作是否存在 if (self.operationCache[app.icon] != nil) { NSLog(@"正在玩命下载中..."); return; } // 3. 定义下载操作 __weak typeof(self) weakSelf = self; NSBlockOperation *downloadOp = [NSBlockOperation blockOperationWithBlock:^{ // 1. 模拟延时 NSLog(@"正在下载 %@", app.name); if (indexPath.row == 0) { [NSThread sleepForTimeInterval:3.0]; } // 2. 异步加载网络图片 NSURL *url = [NSURL URLWithString:app.icon]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; // 3. 主线程更新 UI [[NSOperationQueue mainQueue] addOperationWithBlock:^{ // 将下载操作从缓冲池中删除 [weakSelf.operationCache removeObjectForKey:app.icon]; if (image != nil) { // 设置模型中的图像 [weakSelf.imageCache setObject:image forKey:app.icon]; // 刷新表格 [weakSelf.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; } }]; }]; // 4. 将操作添加到操作缓冲池 [self.operationCache setObject:downloadOp forKey:app.icon]; // 5. 将下载操作添加到队列 [self.downloadQueue addOperation:downloadOp]; }

9.内存警告

如果接收到内存警告,程序一定要做处理,否则后果很严重!!!

- (void)didReceiveMemoryWarning {[super didReceiveMemoryWarning];// 1. 取消下载操作[self.downloadQueue cancelAllOperations]; // 2. 清空缓冲池 [self.operationCache removeAllObjects]; [self.imageCache removeAllObjects]; }

10.沙盒缓存实现

沙盒目录介绍
  • Documents
    • 保存由应用程序产生的文件或者数据,例如:涂鸦程序生成的图片,游戏关卡记录
    • iCloud 会自动备份 Document 中的所有文件
    • 如果保存了从网络下载的文件,在上架审批的时候,会被拒!
  • tmp

    • 临时文件夹,保存临时文件
    • 保存在 tmp 文件夹中的文件,系统会自动回收,譬如磁盘空间紧张或者重新启动手机
    • 程序员不需要管 tmp 文件夹中的释放
  • Caches

    • 缓存,保存从网络下载的文件,后续仍然需要继续使用,例如:网络下载的缓存数据,图片
    • Caches目录下面的文件,当手机存储空间不足的时候,会自动删除
    • 要求程序必需提供一个完善的清除缓存目录的"解决方案"!
  • Preferences

    • 系统偏好,用户偏好
    • 操作是通过 [NSUserDefaults standardDefaults] 来直接操作
NSString+Path
#import "NSString+Path.h"@implementation NSString (Path) - (NSString *)appendDocumentPath { NSString *dir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).lastObject; return [dir stringByAppendingPathComponent:self.lastPathComponent]; } - (NSString *)appendCachePath { NSString *dir = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject; return [dir stringByAppendingPathComponent:self.lastPathComponent]; } - (NSString *)appendTempPath { return [NSTemporaryDirectory() stringByAppendingPathComponent:self.lastPathComponent]; } @end
沙盒缓存
  • 将图像保存至沙盒
if (data != nil) {[data writeToFile:app.icon.appendCachePath atomically:true]; }
  • 检查沙盒缓存
// 判断沙盒文件是否存在
UIImage *image = [UIImage imageWithContentsOfFile:app.icon.appendCachePath]; if (image != nil) { NSLog(@"从沙盒加载图像 ... %@", app.name); // 将图像添加至图像缓存 [self.imageCache setObject:image forKey:app.icon]; cell.iconView.image = image; return cell; }

11.SDWebImage初体验

简介
  • iOS中著名的牛逼的网络图片处理框架
  • 包含的功能:图片下载、图片缓存、下载进度监听、gif处理等等
  • 用法极其简单,功能十分强大,大大提高了网络图片的处理效率
  • 国内超过90%的iOS项目都有它的影子
  • 框架地址:https://github.com/rs/SDWebImage
演示 SDWebImage
  • 导入框架
  • 添加头文件
#import "UIImageView+WebCache.h"
  • 设置图像
[cell.iconView sd_setImageWithURL:[NSURL URLWithString:app.icon]];
思考:SDWebImage 是如何实现的?
  • 将网络图片的异步加载功能封装在 UIImageView 的分类中
  • 与 UITableView 完全解耦

要实现这一目标,需要解决以下问题:

  • 给 UIImageView 下载图像的功能
  • 要解决表格滚动时,因为图像下载速度慢造成的图片错行问题,可以在给 UIImageView 设置新的 URL 时,取消之前未完成的下载操作

目标锁定:取消正在执行中的操作!

12.小结

代码实现回顾
  • 从 tableView 数据源方法入手
  • 根据 indexPath 异步加载网络图片
  • 使用操作缓冲池避免下载操作重复被创建
  • 使用图像缓冲池实现内存缓存,同时能够对内存警告做出响应
  • 使用沙盒缓存实现再次运行程序时,直接从沙盒加载图像,提高程序响应速度,节约用户网络流量
遗留问题
  • 代码耦合度太高,由于下载功能是与数据源的 indexPath 绑定的,如果想将下载图像抽取到 cell 中,难度很大!

二. 仿SDWebImage

  • 目标:模拟 SDWebImage 的实现
  • 说明:整体代码与异步加载图片基本一致,只是编写顺序会有变化!

1.下载操作实现

#import "NSString+Path.h"@interface DownloadImageOperation() /// 要下载图像的 URL 字符串 @property (nonatomic, copy) NSString *URLString; /// 完成回调 Block @property (nonatomic, copy) void (^finishedBlock)(UIImage *image); @end @implementation DownloadImageOperation + (instancetype)downloadImageOperationWithURLString:(NSString *)URLString finished:(void (^)(UIImage *))finished { DownloadImageOperation *op = [[DownloadImageOperation alloc] init]; op.URLString = URLString; op.finishedBlock = finished; return op; } - (void)main { @autoreleasepool { // 1. NSURL NSURL *url = [NSURL URLWithString:self.URLString]; // 2. 获取二进制数据 NSData *data = [NSData dataWithContentsOfURL:url]; // 3. 保存至沙盒 if (data != nil) { [data writeToFile:self.URLString.appendCachePath atomically:YES]; } if (self.isCancelled) { NSLog(@"下载操作被取消"); return; } // 4. 主线程回调 [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.finishedBlock([UIImage imageWithData:data]); }]; } }

2.测试下载操作

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {int seed = arc4random_uniform((UInt32)self.appList.count); AppInfo *app = self.appList[seed]; // 取消之前的下载操作 if (![app.icon isEqualToString:self.currentURLString]) { // 取消之前操作 [self.operationCache[self.currentURLString] cancel]; } // 记录当前操作 self.currentURLString = app.icon; // 创建下载操作 DownloadImageOperation *op = [DownloadImageOperation downloadImageOperationWithURLString:app.icon finished:^(UIImage *image) { self.iconView.image = image; // 从缓冲池删除操作 [self.operationCache removeObjectForKey:app.icon]; }]; // 将操作添加到缓冲池 [self.operationCache setObject:op forKey:app.icon]; // 将操作添加到队列 [self.downloadQueue addOperation:op]; }
框架结构设计

3.下载管理器

  • 单例实现
+ (instancetype)sharedManager {static id instance;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; }

之所以设计成单例,是为了实现全局的图像下载管理

  • 移植属性和懒加载代码
/// 下载队列
@property (nonatomic, strong) NSOperationQueue *downloadQueue; /// 下载操作缓存 @property (nonatomic, strong) NSMutableDictionary *operationCache; // MARK: - 懒加载 - (NSMutableDictionary *)operationCache { if (_operationCache == nil) { _operationCache = [NSMutableDictionary dictionary]; } return _operationCache; } - (NSOperationQueue *)downloadQueue { if (_downloadQueue == nil) { _downloadQueue = [[NSOperationQueue alloc] init]; } return _downloadQueue; }
  • 定义方法
///  下载指定 URL 的图像
///
///  @param URLString 图像 URL 字符串
///  @param finished  下载完成回调
- (void)downloadImageOperationWithURLString:(NSString *)URLString finished:(void (^)(UIImage *image))finished;
  • 方法实现
- (void)downloadImageOperationWithURLString:(NSString *)URLString finished:(void (^)(UIImage *))finished {// 检查操作缓冲池 if (self.operationCache[URLString] != nil) { NSLog(@"正在玩命下载中,稍安勿躁"); return; } // 创建下载操作 DownloadImageOperation *op = [DownloadImageOperation downloadImageOperationWithURLString:URLString finished:^(UIImage *image) { // 从缓冲池删除操作 [self.operationCache removeObjectForKey:URLString]; // 执行回调 finished(image); }]; // 将操作添加到缓冲池 [self.operationCache setObject:op forKey:URLString]; // 将操作添加到队列 [self.downloadQueue addOperation:op]; }
修改 ViewController 中的代码
  • 删除相关属性和懒加载方法
  • 用下载管理器接管之前的下载方法
// 创建下载操作
[[DownloadImageManager sharedManager] downloadImageOperationWithURLString:self.currentURLString finished:^(UIImage *image) {self.iconView.image = image; }];
  • 增加取消下载功能
///  取消指定 URL 的下载操作
- (void)cancelDownloadWithURLString:(NSString *)URLString {// 1. 从缓冲池中取出下载操作DownloadImageOperation *op = self.operationCache[URLString]; if (op == nil) { return; } // 2. 如果有取消 [op cancel]; // 3. 从缓冲池中删除下载操作 [self.operationCache removeObjectForKey:URLString]; }

运行测试!

缓存管理
  • 定义图像缓存属性
/// 图像缓存
@property (nonatomic, strong) NSMutableDictionary *imageCache;
  • 懒加载
- (NSMutableDictionary *)imageCache {if (_imageCache == nil) {_imageCache = [NSMutableDictionary dictionary];}return _imageCache; }
  • 检测图像缓存方法准备
///  检查图像缓存
///
///  @return 是否存在图像缓存
- (BOOL)chechImageCache {return NO; }
  • 方法调用
// 如果存在图像缓存,直接回调
if ([self chechImageCache]) {finished(self.imageCache[URLString]); return; }
  • 缓存方法实现
- (BOOL)chechImageCache:(NSString *)URLString {// 1. 如果存在内存缓存,直接返回if (self.imageCache[URLString]) { NSLog(@"内存缓存"); return YES; } // 2. 如果存在磁盘缓存 UIImage *image = [UIImage imageWithContentsOfFile:URLString.appendCachePath]; if (image != nil) { // 2.1 加载图像并设置内存缓存 NSLog(@"从沙盒缓存"); [self.imageCache setObject:image forKey:URLString]; // 2.2 返回 return YES; } return NO; }

运行测试

4.自定义 UIImageView

  • 目标:

    • 利用下载管理器获取指定 URLString 的图像,完成后设置 image
    • 如果之前存在未完成的下载,判断是否与给定的 URLString 一致
    • 如果一致,等待下载结束
    • 如果不一致,取消之前的下载操作
  • 定义方法

///  设置指定 URL 字符串的网络图像
///
///  @param URLString 网络图像 URL 字符串
- (void)setImageWithURLString:(NSString *)URLString;
  • 方法实现
@interface WebImageView()
///  当前正在下载的 URL 字符串
@property (nonatomic, copy) NSString *currentURLString; @end @implementation WebImageView - (void)setImageWithURLString:(NSString *)URLString { // 取消之前的下载操作 if (![URLString isEqualToString:self.currentURLString]) { // 取消之前操作 [[DownloadImageManager sharedManager] cancelDownloadWithURLString:self.currentURLString]; } // 记录当前操作 self.currentURLString = URLString; // 创建下载操作 __weak typeof(self) weakSelf = self; [[DownloadImageManager sharedManager] downloadImageOperationWithURLString:URLString finished:^(UIImage *image) { weakSelf.image = image; }]; } @end
  • 修改 ViewController 中的调用代码
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {int seed = arc4random_uniform((UInt32)self.appList.count); AppInfo *app = self.appList[seed]; [self.iconView setImageWithURLString:app.icon]; }
  • 运行时机制 —— 关联对象
// MARK: - 运行时关联对象
const void *HMCurrentURLStringKey = "HMCurrentURLStringKey";- (void)setCurrentURLString:(NSString *)currentURLString { objc_setAssociatedObject(self, HMCurrentURLStringKey, currentURLString, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)currentURLString { return objc_getAssociatedObject(self, HMCurrentURLStringKey); }
  • 为了防止 Cell 重用,取消之前下载操作的同时,清空 image
self.image = nil;

三.关于NSCache缓存

介绍
  • NSCache 是苹果提供的一个专门用来做缓存的类
  • 使用和 NSMutableDictionary 非常相似
  • 是线程安全的
  • 当内存不足的时候,会自动清理缓存
  • 程序开始时,可以指定缓存的数量 & 成本
方法
  • 取值

    • - (id)objectForKey:(id)key;
  • 设置对象,0成本

    • - (void)setObject:(id)obj forKey:(id)key;
  • 设置对象并指定成本

    • - (void)setObject:(id)obj forKey:(id)key cost:(NSUInteger)g;
  • 成本示例,以图片为例:

    • 方案一:缓存 100 张图片
    • 方案二:总缓存成本设定为 10M,以图片的 宽 * 高当作成本,图像像素。这样,无论缓存的多少张照片,只要像素值超过 10M,就会自动清理
    • 结论:在缓存图像时,使用成本,比单纯设置数量要科学!
  • 删除

    • - (void)removeObjectForKey:(id)key;
  • 删除全部

    • - (void)removeAllObjects;
属性
  • @property NSUInteger totalCostLimit;

    • 缓存总成本
  • @property NSUInteger countLimit;

    • 缓存总数量
  • @property BOOL evictsObjectsWithDiscardedContent;

    • 是否自动清理缓存,默认是 YES
代码演练
  • 定义缓存属性
@property (nonatomic, strong) NSCache *cache;
  • 懒加载并设置限制
- (NSCache *)cache {if (_cache == nil) {_cache = [[NSCache alloc] init];_cache.delegate = self; _cache.countLimit = 10; } return _cache; }
  • 触摸事件添加缓存
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {for (int i = 0; i < 20; ++i) { NSString *str = [NSString stringWithFormat:@"%d", i]; NSLog(@"set -> %@", str); [self.cache setObject:str forKey:@(i)]; NSLog(@"set -> %@ over", str); } // 遍历缓存 NSLog(@"------"); for (int i = 0; i < 20; ++i) { NSLog(@"%@", [self.cache objectForKey:@(i)]); } } // 代理方法,仅供观察使用,开发时不建议重写此方法 - (void)cache:(NSCache *)cache willEvictObject:(id)obj { NSLog(@"remove -> %@", obj); }
修改网络图片框架
  • 修改图像缓冲池类型,并移动到 .h 中,以便后续测试
///  图像缓冲池
@property (nonatomic, strong) NSCache *imageCache;
  • 修改懒加载,并设置数量限制
- (NSCache *)imageCache {if (_imageCache == nil) {_imageCache = [[NSCache alloc] init];_imageCache.countLimit = 15; } return _imageCache; }
  • 修改其他几处代码,将 self.imageCache[URLString] 替换为 [self.imageCache setObject:image forKey:URLString];

  • 测试缓存中的图片变化

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {for (AppInfo *app in self.appList) { NSLog(@"%@ %@", [[DownloadImageManager sharedManager].imageCache objectForKey:app.icon], app.name); } }
  • 注册通知,监听内存警告
- (instancetype)init
{self = [super init];if (self) {// 注册通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clearMemory) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; } return self; } // 提示:虽然执行不到,但是写了也无所谓 - (void)dealloc { // 删除通知 [[NSNotificationCenter defaultCenter] removeObserver:self]; }
  • 清理内存
- (void)clearMemory {NSLog(@"%s", __FUNCTION__);// 取消所有下载操作[self.downloadQueue cancelAllOperations]; // 删除缓冲池 [self.operationChache removeAllObjects]; }

注意:内存警告或者超出限制后,缓存中的任何对象,都有可能被清理。

四.一些你应该知道的SDWebImage知识点

1> 图片文件缓存的时间有多长:1周

_maxCacheAge = kDefaultCacheMaxCacheAge

2> SDWebImage 的内存缓存是用什么实现的?

NSCache

3> SDWebImage 的最大并发数是多少?

maxConcurrentDownloads = 6

  • 是程序固定死了,可以通过属性进行调整!

4> SDWebImage 支持动图吗?GIF

#import <ImageIO/ImageIO.h>
[UIImage animatedImageWithImages:images duration:duration];

5> SDWebImage是如何区分不同格式的图像的

  • 根据图像数据第一个字节来判断的!

    • PNG:压缩比没有JPG高,但是无损压缩,解压缩性能高,苹果推荐的图像格式!
    • JPG:压缩比最高的一种图片格式,有损压缩!最多使用的场景,照相机!解压缩的性能不好!
    • GIF:序列桢动图,特点:只支持256种颜色!最流行的时候在1998~1999,有专利的!

6> SDWebImage 缓存图片的名称是怎么确定的!

  • md5

    • 如果单纯使用 文件名保存,重名的几率很高!
    • 使用 MD5 的散列函数!对完整的 URL 进行 md5,结果是一个 32 个字符长度的字符串!

7> SDWebImage 的内存警告是如何处理的!

  • 利用通知中心观察
  • - UIApplicationDidReceiveMemoryWarningNotification 接收到内存警告的通知
    • 执行 clearMemory 方法,清理内存缓存!
  • - UIApplicationWillTerminateNotification 接收到应用程序将要终止通知
    • 执行 cleanDisk 方法,清理磁盘缓存!
  • - UIApplicationDidEnterBackgroundNotification 接收到应用程序进入后台通知
    • 执行 backgroundCleanDisk 方法,后台清理磁盘!
    • 通过以上通知监听,能够保证缓存文件的大小始终在控制范围之内!
    • clearDisk 清空磁盘缓存,将所有缓存目录中的文件,全部删除!
      实际工作,将缓存目录直接删除,再次创建一个同名空目录!

转载于:https://www.cnblogs.com/gzz2016/p/5284331.html

这篇关于2016-03-16 sdwebimage的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/325288

相关文章

cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个?

跨平台系列 cross-plateform 跨平台应用程序-01-概览 cross-plateform 跨平台应用程序-02-有哪些主流技术栈? cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个? cross-plateform 跨平台应用程序-04-React Native 介绍 cross-plateform 跨平台应用程序-05-Flutte

【JavaScript】LeetCode:16-20

文章目录 16 无重复字符的最长字串17 找到字符串中所有字母异位词18 和为K的子数组19 滑动窗口最大值20 最小覆盖字串 16 无重复字符的最长字串 滑动窗口 + 哈希表这里用哈希集合Set()实现。左指针i,右指针j,从头遍历数组,若j指针指向的元素不在set中,则加入该元素,否则更新结果res,删除集合中i指针指向的元素,进入下一轮循环。 /*** @param

FreeRTOS内部机制学习03(事件组内部机制)

文章目录 事件组使用的场景事件组的核心以及Set事件API做的事情事件组的特殊之处事件组为什么不关闭中断xEventGroupSetBitsFromISR内部是怎么做的? 事件组使用的场景 学校组织秋游,组长在等待: 张三:我到了 李四:我到了 王五:我到了 组长说:好,大家都到齐了,出发! 秋游回来第二天就要提交一篇心得报告,组长在焦急等待:张三、李四、王五谁先写好就交谁的

Vue day-03

目录 Vue常用特性 一.响应更新 1. 1 v-for更新监测 1.2 v-for就地更新 1.3 什么是虚拟DOM 1.4 diff算法更新虚拟DOM 总结:key值的作用和注意点: 二.过滤器 2.1 vue过滤器-定义使用 2.2 vue过滤器-传参和多过滤器 三. 计算属性(computed) 3.1 计算属性-定义使用 3.2 计算属性-缓存 3.3 计算属

16 子组件和父组件之间传值

划重点 子组件 / 父组件 定义组件中:props 的使用组件中:data 的使用(有 return 返回值) ; 区别:Vue中的data (没有返回值);组件方法中 emit 的使用:emit:英文原意是:触发、发射 的意思components :直接在Vue的方法中声明和绑定要使用的组件 小炒肉:温馨可口 <!DOCTYPE html><html lang="en"><head><

react笔记 8-16 JSX语法 定义数据 数据绑定

1、jsx语法 和vue一样  只能有一个根标签 一行代码写法 return <div>hello world</div> 多行代码返回必须加括号 return (<div><div>hello world</div><div>aaaaaaa</div></div>) 2、定义数据 数据绑定 constructor(){super()this.state={na

打靶记录16——Momentum

靶机: https://download.vulnhub.com/momentum/Momentum.ova 下载后使用 VirtualBox 打开 难度:中 目标:取得 root 权限 + 2 Flag 攻击方法: 主机发现端口扫描信息收集Web 路径爆破XSS 漏洞JS 脚本分析AES 解密Redis 认证漏洞 主机发现 sudo arp-scan -l 端口扫描和服务发

【SpringMVC学习03】-SpringMVC的配置文件详解

在SpringMVC的各个组件中,处理器映射器、处理器适配器、视图解析器称为springmvc的三大组件。其实真正需要程序员开发的就两大块:一个是Handler,一个是jsp。 在springMVC的入门程序中,SpringMVC的核心配置文件——springmvc.xml为: <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http:

浙大数据结构——03-树1 树的同构

这道题我依然采用STL库的map,从而大幅减少了代码量 简单说一下思路,两棵树是否同构,只需比较俩树字母相同的结点是否同构,即是否左==左,右==右或者左==右,右==左。 1、条件准备 atree和btree是存两个数结点字母,第几个就存输入的第几个结点的字母。 map通过结点的字母作为键,从而找到两个子节点的信息 都要用char类型 #include <iostream>#inc

python+selenium2轻量级框架设计-03读取配置文件

任何一个项目,都涉及到了配置文件和管理和读写,Python支持很多配置文件的读写,这里介绍读取ini文件。 以读取url和浏览器作为例子 #浏览器引擎类import configparser,time,osfrom selenium import webdriverfrom framework.logger import Loggerlogger = Logger(logger='