iOS - ImageCache 网络图片缓存

时间:2023-03-08 22:11:33
iOS - ImageCache      网络图片缓存

1、ImageCache

  • 使用内存缓存方式:

    iOS - ImageCache      网络图片缓存

  • 使用沙盒缓存方式:

    iOS - ImageCache      网络图片缓存

  • 使用网络图片第三方库方式:

    • SDWebImage:

      • iOS 中著名的网络图片处理框架

      • 包含的功能:图片下载、图片缓存、下载进度监听、gif 处理等等

      • 用法极其简单,功能十分强大,大大提高了网络图片的处理效率

      • 国内超过 90% 的 iOS 项目都有它的影子

      • 1、图片文件缓存的时间有多长?

        • 1 周
        	_maxCacheAge = kDefaultCacheMaxCacheAge;                                    // - (id)initWithNamespace: diskCacheDirectory:
        
        	static const NSInteger kDefaultCacheMaxCacheAge = 60 * 60 * 24 * 7;         // 1 week   SDImageCache.m
      • 2、SDWebImage 的内存缓存是用什么实现的?

        • NSCache
      • 3、SDWebImage 的最大并发数是多少?

        • 是程序固定死了,可以通过属性进行调整!
        	_downloadQueue.maxConcurrentOperationCount = 6;                             // SDWebImageDownloader.m   - (id)init
      • 4、网络访问超时时长

        • iOS 开发中,默认的访问时长是 1 分钟,SDWebImage 中是 15 秒。
        	_downloadTimeout = 15.0;                                                    // SDWebImageDownloader.m   - (id)init
      • 5、SDWebImage 支持动图吗?GIF

        • 支持
        	#import <ImageIO/ImageIO.h>                                                 // UIImage+GIF.m
        [UIImage animatedImageWithImages:images duration:duration];
      • 6、SDWebImage 是如何区分不同格式的图像的

        • 根据图像数据第一个字节来判断的!
        	// NSData+ImageContentType.m    + (NSString *)sd_contentTypeForImageData:
        
        	PNG:0x89 image/png ,压缩比没有 JPG 高,但是无损压缩,解压缩性能高,苹果推荐的图像格式!
        JPG:0xFF image/jpeg,压缩比最高的一种图片格式,有损压缩!最多使用的场景,照相机!解压缩的性能不好!
        GIF:0x47 image/gif ,序列桢动图,特点:只支持 256 种颜色!最流行的时候在 1998~1999,有专利的!
      • 7、SDWebImage 缓存图片的名称是怎么确定的!

        • md5

        • 如果单纯使用 文件名保存,重名的几率很高!

        • 使用 MD5 的散列函数!对完整的 URL 进行 md5,结果是一个 32 个字符长度的字符串!

      • 8、SDWebImage 的内存警告是如何处理的!

        • 利用通知中心观察

        • UIApplicationDidReceiveMemoryWarningNotification 接收到内存警告的通知

        • 执行 clearMemory 方法,清理内存缓存!

        • UIApplicationWillTerminateNotification 接收到应用程序将要终止通知

        • 执行 cleanDisk 方法,清理磁盘缓存!

        • UIApplicationDidEnterBackgroundNotification 接收到应用程序进入后台通知

        • 执行 backgroundCleanDisk 方法,后台清理磁盘!

        • 通过以上通知监听,能够保证缓存文件的大小始终在控制范围之内!

        • clearDisk 清空磁盘缓存,将所有缓存目录中的文件,全部删除! 实际工作,将缓存目录直接删除,再次创建一个同名空目录!

      • bug:

        • SDWebImage 中,一旦内存警告,清理了内存之后,之后所有的图片都是从沙盒加载的。

        • 原因:NSCache 中一旦调用了 removeAllObjects,就无法给 cache 添加对象。关于 NSCache 的内存管理,交给他自己就行!

2、自定义内存缓存方式

  • Objective-C

    • AppInfoModel.h

      	@interface AppInfoModel : NSObject
      
      	/// 标题名称
      @property (nonatomic, strong) NSString *name; /// 下载数量
      @property (nonatomic, strong) NSString *download; /// 图片地址
      @property (nonatomic, strong) NSString *icon; /// 声明工厂方法,数据源初始化方法
      + (instancetype)appInfoModelWithDict:(NSDictionary *)dict; @end
    • AppInfoModel.m

      	// 实现工厂方法
      + (instancetype)appInfoModelWithDict:(NSDictionary *)dict { id obj = [[self alloc] init]; [obj setValuesForKeysWithDictionary:dict]; return obj;
      }
    • AppInfoCell.h

      	@interface AppInfoCell : UITableViewCell
      
      	@property (nonatomic, weak) IBOutlet UILabel *nameLabel;
      @property (nonatomic, weak) IBOutlet UILabel *downloadLabel;
      @property (nonatomic, weak) IBOutlet UIImageView *iconImageView; @end
    • ViewController.m

      	/// 表格数据源
      @property (nonatomic, strong) NSArray *dataSourceArray; /// 图片下载队列
      @property (nonatomic, strong) NSOperationQueue *downloadQueue; /// 下载缓冲池
      @property (nonatomic, strong) NSMutableDictionary *downloadQueueCache; /// 图片缓冲池
      @property (nonatomic, strong) NSMutableDictionary *imageCache; /// 图片下载地址黑名单
      @property (nonatomic, retain) NSMutableArray *urlBlackList; // 懒加载 - (NSArray *)dataSourceArray {
      if (_dataSourceArray == nil) {
      NSArray *array = [NSArray arrayWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"apps.plist" withExtension:nil]]; NSMutableArray *arrayM = [NSMutableArray arrayWithCapacity:array.count];
      [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
      [arrayM addObject:[AppInfoModel appInfoModelWithDict:obj]];
      }];
      _dataSourceArray = [arrayM copy];
      }
      return _dataSourceArray;
      } - (NSOperationQueue *)downloadQueue {
      if (_downloadQueue == nil) {
      _downloadQueue = [[NSOperationQueue alloc] init];
      }
      return _downloadQueue;
      } - (NSMutableDictionary *)downloadQueueCache {
      if (_downloadQueueCache == nil) {
      _downloadQueueCache = [[NSMutableDictionary alloc] init];
      }
      return _downloadQueueCache;
      } - (NSMutableDictionary *)imageCache {
      if (_imageCache == nil) {
      _imageCache = [[NSMutableDictionary alloc] init];
      }
      return _imageCache;
      } - (NSMutableArray *)urlBlackList {
      if (_urlBlackList == nil) {
      _urlBlackList = [[NSMutableArray alloc] init];
      }
      return _urlBlackList;
      } // 表格视图数据源方法 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
      return self.dataSourceArray.count;
      } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { AppInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppCell" forIndexPath:indexPath]; AppInfoModel *dataModel = self.dataSourceArray[indexPath.row]; cell.nameLabel.text = dataModel.name;
      cell.downloadLabel.text = dataModel.download; // 判断图片缓存池中是否有相应的图片
      if (self.imageCache[dataModel.icon] != nil) { // 从缓存池中取出图片显示在 Cell 上
      cell.iconImageView.image = self.imageCache[dataModel.icon];
      } else { // 从网络异步下载图片
      [self downloadImageWithIndexPath:indexPath];
      } return cell;
      } - (void)downloadImageWithIndexPath:(NSIndexPath *)indexPath { AppInfoModel *dataModel = self.dataSourceArray[indexPath.row]; // 判断下载缓冲池中是否存在当前下载操作
      if (self.downloadQueueCache[dataModel.icon] != nil) {
      return;
      } // 判断图片地址是否在黑名单中
      if ([self.urlBlackList containsObject:dataModel.icon]) {
      return;
      } // 创建异步下载图片操作
      NSBlockOperation *downloadOperation = [NSBlockOperation blockOperationWithBlock:^{ UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:dataModel.icon]]]; // 下载完成从下载缓冲池中删除当前下载操作
      [self.downloadQueueCache removeObjectForKey:dataModel.icon]; // 添加黑名单记录
      if (image == nil && ![self.urlBlackList containsObject:dataModel.icon]) {
      [self.urlBlackList addObject:dataModel.icon];
      } // 主线程跟新 UI
      [[NSOperationQueue mainQueue] addOperationWithBlock:^{ if (image != nil) { // 将下载完成的图片保存到图片缓冲池中
      [self.imageCache setObject:image forKey:dataModel.icon]; [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
      }
      }];
      }]; // 将当前下载添加到下载缓冲池中
      [self.downloadQueueCache setObject:downloadOperation forKey:dataModel.icon]; // 开始异步下载图片
      [self.downloadQueue addOperation:downloadOperation];
      } // 内存警告
      /*
      日常上课通常就直接删除,但是在工作后,必须要处理!不处理后果很严重,第一次内存警告如果不处理,就没有第二次的机会了,就直接被闪退了
      */ - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // 清理缓冲池
      [self.downloadQueueCache removeAllObjects];
      [self.imageCache removeAllObjects]; // 取消下载操作,等用户再滚动表格,调用数据源方法,又能够自动下载
      [self.downloadQueue cancelAllOperations];
      }
  • Swift

    • AppInfo.swift

      	class AppInfo: NSObject {
      
          	var name:String!
      var icon:String!
      var download:String! class func AppInfoWithDict(dict:[String:AnyObject]) -> AnyObject {
      let obj:AnyObject = self.init() obj.setValuesForKeysWithDictionary(dict) return obj
      } required override init() {
      super.init()
      }
      }
    • AppInfoCell.swift

      	class AppInfoCell: UITableViewCell {
      
          	@IBOutlet weak var iconView: UIImageView!
      @IBOutlet weak var nameLabel: UILabel!
      @IBOutlet weak var downLabel: UILabel!
      }
    • ViewController.swift

      	// 懒加载
      
          	// 数据源
      lazy var dataSourceArray:[AnyObject] = {
      var dataArray:[AnyObject] = Array() let array = NSArray(contentsOfURL: NSBundle.mainBundle().URLForResource("apps.plist", withExtension: nil)!)
      array!.enumerateObjectsUsingBlock { (obj:AnyObject, idx:Int, stop:UnsafeMutablePointer<ObjCBool>) in
      dataArray.append(AppInfoModel.AppInfoWithDict(obj as! [String : AnyObject]))
      }
      return dataArray
      }() // 下载队列
      lazy var downloadQueue:NSOperationQueue = {
      var tmp:NSOperationQueue = NSOperationQueue()
      return tmp
      }() // 下载缓冲池
      lazy var downloadQueueCache:[String:NSBlockOperation] = {
      var tmp:[String:NSBlockOperation] = Dictionary()
      return tmp
      }() // 图片缓冲池
      lazy var imageCache:[String:UIImage] = {
      var tmp:[String:UIImage] = Dictionary()
      return tmp
      }() // 图片下载地址黑名单
      lazy var urlBlackList:[String] = {
      var tmp:[String] = Array()
      return tmp
      }() // 表格视图数据源方法 override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      return self.dataSourceArray.count
      } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell:AppInfoCell = tableView.dequeueReusableCellWithIdentifier("AppCell", forIndexPath: indexPath) as! AppInfoCell let dataModel = self.dataSourceArray[indexPath.row] as! AppInfoModel cell.nameLabel.text = dataModel.name
      cell.downLabel.text = dataModel.download // 判断图片缓存池中是否有相应的图片
      if self.imageCache[dataModel.icon] != nil { // 从缓存池中取出图片显示在 Cell 上
      cell.iconView.image = self.imageCache[dataModel.icon]
      } else { // 从网络异步下载图片
      self.downloadImageWithIndexPath(indexPath)
      }
      return cell
      } func downloadImageWithIndexPath(indexPath:NSIndexPath) { let dataModel = self.dataSourceArray[indexPath.row] as! AppInfoModel // 判断下载缓冲池中是否存在当前下载操作
      if self.downloadQueueCache[dataModel.icon] != nil {
      return
      } // 判断图片地址是否在黑名单中
      if self.urlBlackList.contains(dataModel.icon) {
      return
      } // 创建异步下载图片操作
      let downloadOperation = NSBlockOperation { let image:UIImage? = UIImage(data: NSData(contentsOfURL: NSURL(string: dataModel.icon)!)!) // 下载完成从下载缓冲池中删除当前下载操作
      self.downloadQueueCache.removeValueForKey(dataModel.icon) // 添加黑名单记录
      if image == nil && !self.urlBlackList.contains(dataModel.icon) { self.urlBlackList.append(dataModel.icon)
      } // 主线程跟新 UI
      NSOperationQueue.mainQueue().addOperationWithBlock({ if image != nil { // 将下载完成的图片保存到图片缓冲池中
      self.imageCache[dataModel.icon] = image self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
      }
      })
      } // 将当前下载添加到下载缓冲池中
      self.downloadQueueCache[dataModel.icon] = downloadOperation // 开始异步下载图片
      self.downloadQueue.addOperation(downloadOperation)
      } // 内存警告 override func didReceiveMemoryWarning() {
      super.didReceiveMemoryWarning() self.downloadQueueCache.removeAll()
      self.imageCache.removeAll() self.downloadQueue.cancelAllOperations()
      }

3、自定义沙盒缓存方式

  • Objective-C

    • AppInfoModel.h

    • AppInfoModel.m

    • AppInfoCell.h

      • 与上边一样
    • NSString+BundlePath.h

      	///  拼接缓存目录
      - (NSString *)appendCachePath;
    • NSString+BundlePath.m

      	- (NSString *)appendCachePath {
      NSString *dir = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject;
      return [dir stringByAppendingPathComponent:self.lastPathComponent];
      }
    • ViewController.m

      	// 懒加载
      
      		与上边一样
      
      	// 表格视图数据源方法
      
          	- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
      return self.dataSourceArray.count;
      } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { AppInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppCell" forIndexPath:indexPath]; AppInfoModel *dataModel = self.dataSourceArray[indexPath.row]; cell.nameLabel.text = dataModel.name;
      cell.downloadLabel.text = dataModel.download; // 判断图片缓存池中是否有相应的图片
      if (self.imageCache[dataModel.icon] != nil) { // 从缓存池中取出图片显示在 Cell 上
      cell.iconImageView.image = self.imageCache[dataModel.icon];
      } else { // 从沙盒加载图片
      UIImage *image = [UIImage imageWithContentsOfFile:[dataModel.icon appendCachePath]]; if (image != nil) { cell.iconImageView.image = image;
      self.imageCache[dataModel.icon] = image; } else { // 从网络异步下载图片
      [self downloadImageWithIndexPath:indexPath];
      }
      } return cell;
      } - (void)downloadImageWithIndexPath:(NSIndexPath *)indexPath { AppInfoModel *dataModel = self.dataSourceArray[indexPath.row]; // 判断下载缓冲池中是否存在当前下载操作
      if (self.downloadQueueCache[dataModel.icon] != nil) {
      return;
      } // 判断图片地址是否在黑名单中
      if ([self.urlBlackList containsObject:dataModel.icon]) { return;
      } // 创建异步下载图片操作
      NSBlockOperation *downloadOperation = [NSBlockOperation blockOperationWithBlock:^{ NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:dataModel.icon]];
      UIImage *image = [UIImage imageWithData:imageData]; // 下载完成从下载缓冲池中删除当前下载操作
      [self.downloadQueueCache removeObjectForKey:dataModel.icon]; // 添加黑名单记录
      if (image == nil && ![self.urlBlackList containsObject:dataModel.icon]) {
      [self.urlBlackList addObject:dataModel.icon];
      } if (image != nil) { // 将图像写入沙盒
      [imageData writeToFile:[dataModel.icon appendCachePath] atomically:YES];
      } // 主线程跟新 UI
      [[NSOperationQueue mainQueue] addOperationWithBlock:^{ if (image != nil) { // 将下载完成的图片保存到图片缓冲池中
      [self.imageCache setObject:image forKey:dataModel.icon]; [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
      }
      }];
      }]; // 将当前下载添加到下载缓冲池中
      [self.downloadQueueCache setObject:downloadOperation forKey:dataModel.icon]; // 开始异步下载图片
      [self.downloadQueue addOperation:downloadOperation];
      } // 内存警告 与上边一样
  • Swift

    • AppInfo.swift

    • AppInfoCell.swift

      • 与上边一样
    • String+BundlePath.swift

      	public func appendCachePath() -> String? {
      let dir:String? = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true).last
      if (dir != nil) {
      return dir! + "/" + (self as NSString).lastPathComponent
      } else {
      return nil
      }
      }
    • ViewController.swift

      	// 懒加载
      
      		与上边一样
      
      	// 表格视图数据源方法
      
          	override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      return self.dataSourceArray.count
      } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell:AppInfoCell = tableView.dequeueReusableCellWithIdentifier("AppCell", forIndexPath: indexPath) as! AppInfoCell let dataModel = self.dataSourceArray[indexPath.row] as! AppInfoModel cell.nameLabel.text = dataModel.name
      cell.downLabel.text = dataModel.download // 判断图片缓存池中是否有相应的图片
      if self.imageCache[dataModel.icon] != nil { // 从缓存池中取出图片显示在 Cell 上
      cell.iconView.image = self.imageCache[dataModel.icon] } else { // 从沙盒加载图片
      let image = UIImage(contentsOfFile: dataModel.icon.appendCachePath()!) if (image != nil) { cell.iconView.image = image
      self.imageCache[dataModel.icon] = image } else { // 从网络异步下载图片
      self.downloadImageWithIndexPath(indexPath)
      }
      } return cell
      } func downloadImageWithIndexPath(indexPath:NSIndexPath) { let dataModel = self.dataSourceArray[indexPath.row] as! AppInfoModel // 判断下载缓冲池中是否存在当前下载操作
      if self.downloadQueueCache[dataModel.icon] != nil { return
      } // 判断图片地址是否在黑名单中
      if self.urlBlackList.contains(dataModel.icon) { return
      } // 创建异步下载图片操作
      let downloadOperation = NSBlockOperation { let imageData:NSData? = NSData(contentsOfURL: NSURL(string: dataModel.icon)!) let image:UIImage? = UIImage(data: imageData!) // 下载完成从下载缓冲池中删除当前下载操作
      self.downloadQueueCache.removeValueForKey(dataModel.icon) // 添加黑名单记录
      if image == nil && !self.urlBlackList.contains(dataModel.icon) { self.urlBlackList.append(dataModel.icon)
      } if image != nil { // 将图像写入沙盒
      imageData!.writeToFile(dataModel.icon.appendCachePath()!, atomically: true) print(dataModel.icon.appendCachePath()!)
      } // 主线程跟新 UI
      NSOperationQueue.mainQueue().addOperationWithBlock({ if image != nil { // 将下载完成的图片保存到图片缓冲池中
      self.imageCache[dataModel.icon] = image self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
      }
      })
      } // 将当前下载添加到下载缓冲池中
      self.downloadQueueCache[dataModel.icon] = downloadOperation // 开始异步下载图片
      self.downloadQueue.addOperation(downloadOperation)
      } // 内存警告 与上边一样

4、仿 SDWebImage 缓存方式

  • Objective-C

    • AppInfoModel.h

      	@interface AppInfoModel : NSObject
      
      	/// 标题名称
      @property (nonatomic, strong) NSString *name; /// 下载数量
      @property (nonatomic, strong) NSString *download; /// 图片地址
      @property (nonatomic, strong) NSString *icon; /// 从 Plist 加载 AppInfo
      + (NSArray *)loadPList; @end
    • AppInfoModel.m

      	+ (NSArray *)loadPList {
      
          	NSArray *array = [NSArray arrayWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"apps.plist" withExtension:nil]];
      
          	NSMutableArray *plist = [NSMutableArray arrayWithCapacity:array.count];
      [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { id model = [[self alloc] init];
      [model setValuesForKeysWithDictionary:obj]; [plist addObject:model];
      }]; return plist;
      }
    • AppInfoCell.h

      	@interface AppInfoCell : UITableViewCell
      
      	@property (nonatomic, weak) IBOutlet UILabel *nameLabel;
      @property (nonatomic, weak) IBOutlet UILabel *downloadLabel;
      @property (nonatomic, weak) IBOutlet UIImageView *iconImageView; @end
    • NSString+BundlePath.h

      	///  拼接缓存目录
      - (NSString *)appendCachePath;
    • NSString+BundlePath.m

      	- (NSString *)appendCachePath {
      NSString *dir = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).lastObject;
      return [dir stringByAppendingPathComponent:self.lastPathComponent];
      }
    • WebImageOperation.h

      	@interface WebImageOperation : NSOperation
      
      	///  实例化 web 图像操作
      + (instancetype)webImageOperationWithURLString:(NSString *)urlString completion:(void (^)(UIImage *image))completion; @end
    • WebImageOperation.m

      	/// 下载图片的 URL
      @property (nonatomic, copy) NSString *urlStr; /// 下载完成的回调
      @property (nonatomic, copy) void (^completion) (UIImage *image); + (instancetype)webImageOperationWithURLString:(NSString *)urlString completion:(void (^)(UIImage *))completion { WebImageOperation *imageOperation = [[self alloc] init]; imageOperation.urlStr= urlString;
      imageOperation.completion = completion; return imageOperation;
      } // 操作加入队列后会自动执行该方法
      - (void)main {
      @autoreleasepool { if (self.isCancelled) return; NSURL *url = [NSURL URLWithString:self.urlStr];
      NSData *data = [NSData dataWithContentsOfURL:url]; if (self.isCancelled) return; if (data != nil) {
      [data writeToFile:self.urlStr.appendCachePath atomically:YES];
      } if (self.isCancelled) return; if (self.completion && data != nil) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.completion([UIImage imageWithData:data]);
      }];
      }
      }
      }
    • WebImageManager.h

      	//  负责所有网络图像的下载操作以及缓存管理!
      @interface WebImageManager : NSObject /// 全局单例访问入口
      + (instancetype)sharedManager; /// 下载网络图像
      - (void)downloadImage:(NSString *)urlString completion:(void (^) (UIImage *image))completion; /// 取消 urlString 对应的下载操作
      - (void)cancelDownload:(NSString *)urlString; @end
    • WebImageManager.m

      	/// 下载队列
      @property (nonatomic, strong) NSOperationQueue *downloadQueue; /// 下载操作缓冲池
      @property (nonatomic, strong) NSMutableDictionary *downloadQueueCache; /// 图片缓冲池
      @property (nonatomic, strong) NSMutableDictionary *imageCache; // 下载管理器 // 实例化下载管理器
      + (instancetype)sharedManager { static id instance; static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
      instance = [[self alloc] init];
      });
      return instance;
      } // 下载操作 - (void)downloadImage:(NSString *)urlString completion:(void (^)(UIImage *))completion { // 判断缓存中是否存在图像
      if ([self checkCacheWithURLString:urlString]) { if (completion != nil) { // 直接回调,传递给调用方图像
      completion(self.imageCache[urlString]);
      } return;
      } // 判断缓冲池中是否存在下载操作
      if (self.downloadQueueCache[urlString] != nil) { return;
      } WebImageOperation *downloadOperation = [WebImageOperation webImageOperationWithURLString:urlString
      completion:^(UIImage *image) { // 下载完成从操作缓冲池中移除操作
      [self.downloadQueueCache removeObjectForKey:urlString]; // 下载完成添加到图片缓冲池中
      [self.imageCache setObject:image forKey:urlString]; if (completion != nil) {
      completion(image);
      }
      }]; // 将操作添加到缓冲池
      [self.downloadQueueCache setObject:downloadOperation forKey:urlString]; // 将操作添加到队列
      [self.downloadQueue addOperation:downloadOperation];
      } // 取消 urlString 对应的下载操作
      - (void)cancelDownload:(NSString *)urlString { // 从缓冲池拿到下载操作
      WebImageOperation *downloadOperation = self.downloadQueueCache[urlString]; if (downloadOperation != nil) { // 取消操作
      [downloadOperation cancel]; // 从缓冲池中删除操作
      [self.downloadQueueCache removeObjectForKey:urlString];
      }
      } // 判断缓存中是否存在图像
      - (BOOL)checkCacheWithURLString:(NSString *)urlString { // 判断图片缓冲池中是否存在图像
      if (self.imageCache[urlString] != nil) {
      return YES;
      } UIImage *image = [UIImage imageWithContentsOfFile:[urlString appendCachePath]]; // 判断沙盒中是否存在图像
      if (image != nil) { [self.imageCache setObject:image forKey:urlString]; return YES;
      } return NO;
      } // 懒加载 - (NSOperationQueue *)downloadQueue {
      if (_downloadQueue == nil) {
      _downloadQueue = [[NSOperationQueue alloc] init];
      }
      return _downloadQueue;
      } - (NSMutableDictionary *)downloadQueueCache {
      if (_downloadQueueCache == nil) {
      _downloadQueueCache = [[NSMutableDictionary alloc] init];
      }
      return _downloadQueueCache;
      } - (NSMutableDictionary *)imageCache {
      if (_imageCache == nil) {
      _imageCache = [[NSMutableDictionary alloc] init];
      }
      return _imageCache;
      }
    • UIImageView+WebImageView.h

      	@interface UIImageView (WebImageView)
      
      	/// 设置 Web 图像 URL,自动加载图像
      - (void)setWebImageWithURL:(NSString *)urlString; @end
    • UIImageView+WebImageView.m

      	#import <objc/runtime.h>
      
      	// 下载图片的 url
      @property (nonatomic, copy) NSString *urlStr; - (void)setWebImageWithURL:(NSString *)urlString { // 屏蔽快速滑动重复添加下载
      if ([self.urlStr isEqualToString:urlString]) { return;
      } // 暂停之前的操作
      if (self.urlStr != nil && ![self.urlStr isEqualToString:urlString]) { [[WebImageManager sharedManager] cancelDownload:self.urlStr]; // 如果 ImageView 之前有图像-清空图像
      self.image = nil;
      } // 记录新的 url
      self.urlStr = urlString; __weak typeof(self) weakSelf = self; // 下载网络图片
      [[WebImageManager sharedManager] downloadImage:self.urlStr completion:^(UIImage *image) {
      weakSelf.image = image;
      }];
      } // 向分类添加属性 // 运行时的关联对象,动态添加属性
      const void *URLStrKey = "URLStrKey"; - (void)setUrlStr:(NSString *)urlString {
      objc_setAssociatedObject(self, URLStrKey, urlString, OBJC_ASSOCIATION_COPY_NONATOMIC);
      } - (NSString *)urlStr {
      return objc_getAssociatedObject(self, URLStrKey);
      }
    • ViewController.m

      	/// 表格数据源
      @property (nonatomic, strong) NSArray *dataSourceArray; // 懒加载 - (NSArray *)dataSourceArray {
      if (_dataSourceArray == nil) {
      _dataSourceArray = [AppInfoModel loadPList];
      }
      return _dataSourceArray;
      } // 表格视图数据源方法 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
      return self.dataSourceArray.count;
      } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { AppInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppCell" forIndexPath:indexPath]; AppInfoModel *dataModel = self.dataSourceArray[indexPath.row]; cell.nameLabel.text = dataModel.name;
      cell.downloadLabel.text = dataModel.download; [cell.iconImageView setWebImageWithURL:dataModel.icon]; return cell;
      }
  • Swift

    • AppInfoModel.swift

      	class AppInfoModel: NSObject
      
      	var name:String!
      var icon:String!
      var download:String! class func loadPList() -> [AnyObject] {
      let array = NSArray(contentsOfURL: NSBundle.mainBundle().URLForResource("apps.plist", withExtension: nil)!) var plist:[AnyObject] = Array()
      array!.enumerateObjectsUsingBlock { (obj:AnyObject, idx:Int, stop:UnsafeMutablePointer<ObjCBool>) in let model:AnyObject = self.init() model.setValuesForKeysWithDictionary(obj as! [String : AnyObject]) plist.append(model)
      }
      return plist
      } class func AppInfoWithDict(dict:[String:AnyObject]) -> AnyObject {
      let obj:AnyObject = self.init() obj.setValuesForKeysWithDictionary(dict) return obj
      } required override init() {
      super.init()
      }
    • AppInfoCell.swift

      	@IBOutlet weak var iconView: UIImageView!
      @IBOutlet weak var nameLabel: UILabel!
      @IBOutlet weak var downLabel: UILabel!
    • String+BundlePath.swift

      	///  拼接缓存目录
      public func appendCachePath() -> String? {
      let dir:String? = NSSearchPathForDirectoriesInDomains(.CachesDirectory, .UserDomainMask, true).last
      if (dir != nil) {
      return dir! + "/" + (self as NSString).lastPathComponent
      } else {
      return nil
      }
      }
    • WebImageOperation.swift

      	class WebImageOperation: NSOperation
      
      	/// 下载图片的 URL
      var urlStr:String! /// 下载完成的回调
      var completion:((image:UIImage) -> Void)! class func webImageOperationWithURLString(urlString:String, completion:((image:UIImage) -> Void)) -> WebImageOperation { let imageOperation:WebImageOperation = WebImageOperation() imageOperation.urlStr = urlString
      imageOperation.completion = completion return imageOperation
      } // 操作加入队列后会自动执行该方法
      override func main() { if self.cancelled == true {
      return
      } let url:NSURL = NSURL(string: self.urlStr)!
      let data:NSData? = NSData(contentsOfURL: url) if self.cancelled == true {
      return
      } if data != nil { data?.writeToFile(self.urlStr.appendCachePath()!, atomically: true)
      } if self.cancelled == true {
      return
      } if (self.completion != nil) && (data != nil) { NSOperationQueue.mainQueue().addOperationWithBlock({ self.completion(image: UIImage(data: data!)!)
      })
      }
      }
    • WebImageManager.swift

      	// 负责所有网络图像的下载操作以及缓存管理!
      class WebImageManager: NSObject // 下载队列
      lazy var downloadQueue:NSOperationQueue = { var tmp:NSOperationQueue = NSOperationQueue()
      return tmp
      }() // 下载缓冲池
      lazy var downloadQueueCache:[String:WebImageOperation] = { var tmp:[String:WebImageOperation] = Dictionary()
      return tmp
      }() // 图片缓冲池
      lazy var imageCache:[String:UIImage] = { var tmp:[String:UIImage] = Dictionary()
      return tmp
      }() // 下载管理器 static let sharedManager = WebImageManager()
      private override init() {} // 下载操作 func downloadImage(urlString:String, completion:((image:UIImage) -> Void)?) { // 判断缓存中是否存在图像
      if self.checkCacheWithURLString(urlString) == true { if completion != nil { // 直接回调,传递给调用方图像
      completion!(image: self.imageCache[urlString]!)
      } return
      } // 判断缓冲池中是否存在下载操作
      if self.downloadQueueCache[urlString] != nil { print("玩命下载中...稍安勿躁!") return
      } let downloadOperation:WebImageOperation = WebImageOperation.webImageOperationWithURLString(urlString) { (image) in // 下载完成从操作缓冲池中移除操作
      self.downloadQueueCache.removeValueForKey(urlString) // 下载完成添加到图片缓冲池中
      self.imageCache[urlString] = image if (completion != nil) {
      completion!(image: image);
      }
      } // 将操作添加到缓冲池
      self.downloadQueueCache[urlString] = downloadOperation // 将操作添加到队列
      self.downloadQueue.addOperation(downloadOperation)
      } // 取消 urlString 对应的下载操作
      func cancelDownload(urlString:String) { // 从缓冲池拿到下载操作
      let downloadOperation:WebImageOperation? = self.downloadQueueCache[urlString] if downloadOperation != nil { print("取消下载操作") // 取消操作
      downloadOperation!.cancel() // 从缓冲池中删除操作
      self.downloadQueueCache.removeValueForKey(urlString)
      }
      } // 判断缓存中是否存在图像
      func checkCacheWithURLString(urlString:String) -> Bool { // 判断图片缓冲池中是否存在图像
      if self.imageCache[urlString] != nil { print("从内存中加载...") return true
      } let image:UIImage? = UIImage(contentsOfFile: urlString.appendCachePath()!) // 判断沙盒中是否存在图像
      if image != nil { print("从沙盒中加载...") self.imageCache[urlString] = image return true
      } return false
      }
    • UIImageView+WebImageView.h

      	@interface UIImageView (WebImageView)
      
      	/// 设置 Web 图像 URL,自动加载图像
      - (void)setWebImageWithURL:(NSString *)urlString; @end
    • UIImageView+WebImageView.m

      	#import <objc/runtime.h>
      #import "SwiftImageCache-Swift.h" // 下载图片的 url
      @property (nonatomic, copy) NSString *urlStr; - (void)setWebImageWithURL:(NSString *)urlString { // 屏蔽快速滑动重复添加下载
      if ([self.urlStr isEqualToString:urlString]) { return;
      } // 暂停之前的操作
      if (self.urlStr != nil && ![self.urlStr isEqualToString:urlString]) { [[WebImageManager sharedManager] cancelDownload:self.urlStr]; // 如果 ImageView 之前有图像-清空图像
      self.image = nil;
      } // 记录新的 url
      self.urlStr = urlString; __weak typeof(self) weakSelf = self; // 下载网络图片
      [[WebImageManager sharedManager] downloadImage:self.urlStr completion:^(UIImage *image) {
      weakSelf.image = image;
      }];
      } // 向分类添加属性 // 运行时的关联对象,动态添加属性
      const void *URLStrKey = "URLStrKey"; - (void)setUrlStr:(NSString *)urlString {
      objc_setAssociatedObject(self, URLStrKey, urlString, OBJC_ASSOCIATION_COPY_NONATOMIC);
      } - (NSString *)urlStr {
      return objc_getAssociatedObject(self, URLStrKey);
      }
    • SwiftImageCache-Bridging-Header.h

      	#import "UIImageView+WebImageView.h"
    • ViewController.swift

      	// 懒加载
      
          	lazy var dataSourceArray:[AnyObject] = {
      var tmp:[AnyObject] = AppInfoModel.loadPList()
      return tmp
      }() // 表格视图数据源方法 override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      return self.dataSourceArray.count
      } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell:AppInfoCell = tableView.dequeueReusableCellWithIdentifier("AppCell", forIndexPath: indexPath) as! AppInfoCell let dataModel = self.dataSourceArray[indexPath.row] as! AppInfoModel cell.nameLabel.text = dataModel.name
      cell.downLabel.text = dataModel.download cell.iconView.setWebImageWithURL(dataModel.icon) return cell
      }

5、SDWebImage 缓存方式

  • Github 网址:https://github.com/rs/SDWebImage

  • SDWebImage 使用 ARC

  • Objective-C

    	// 添加第三方库文件
    SDWebImage // 包含头文件
    #import "UIImageView+WebCache.h"
    • AppInfoModel.h

      	@interface AppInfoModel : NSObject
      
      	/// 标题名称
      @property (nonatomic, strong) NSString *name; /// 下载数量
      @property (nonatomic, strong) NSString *download; /// 图片地址
      @property (nonatomic, strong) NSString *icon; /// 从 Plist 加载 AppInfo
      + (NSArray *)loadPList; @end
    • AppInfoModel.m

      	+ (NSArray *)loadPList {
      
          	NSArray *array = [NSArray arrayWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"apps.plist" withExtension:nil]];
      
          	NSMutableArray *plist = [NSMutableArray arrayWithCapacity:array.count];
      [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { id model = [[self alloc] init];
      [model setValuesForKeysWithDictionary:obj]; [plist addObject:model];
      }]; return plist;
      }
    • AppInfoCell.h

      	@interface AppInfoCell : UITableViewCell
      
      	@property (nonatomic, weak) IBOutlet UILabel *nameLabel;
      @property (nonatomic, weak) IBOutlet UILabel *downloadLabel;
      @property (nonatomic, weak) IBOutlet UIImageView *iconImageView; @end
    • ViewController.m

      	/// 表格数据源
      @property (nonatomic, strong) NSArray *dataSourceArray; // 懒加载 - (NSArray *)dataSourceArray {
      if (_dataSourceArray == nil) {
      _dataSourceArray = [AppInfoModel loadPList];
      }
      return _dataSourceArray;
      } // 表格视图数据源方法 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
      return self.dataSourceArray.count;
      } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { AppInfoCell *cell = [tableView dequeueReusableCellWithIdentifier:@"AppCell" forIndexPath:indexPath]; AppInfoModel *dataModel = self.dataSourceArray[indexPath.row]; cell.nameLabel.text = dataModel.name;
      cell.downloadLabel.text = dataModel.download; [cell.iconImageView sd_setImageWithURL:[NSURL URLWithString:dataModel.icon]]; return cell;
      }
  • Swift

    	// 添加第三方库文件
    SDWebImage // 创建桥接头文件,如
    SwiftImageCache-Bridging-Header.h // 在桥接头文件中添加头文件
    #import "UIImageView+WebCache.h"
    • AppInfoModel.swift

      	class AppInfoModel: NSObject	
      
      	var name:String!
      var icon:String!
      var download:String! class func loadPList() -> [AnyObject] {
      let array = NSArray(contentsOfURL: NSBundle.mainBundle().URLForResource("apps.plist", withExtension: nil)!) var plist:[AnyObject] = Array()
      array!.enumerateObjectsUsingBlock { (obj:AnyObject, idx:Int, stop:UnsafeMutablePointer<ObjCBool>) in let model:AnyObject = self.init() model.setValuesForKeysWithDictionary(obj as! [String : AnyObject]) plist.append(model)
      }
      return plist
      } class func AppInfoWithDict(dict:[String:AnyObject]) -> AnyObject {
      let obj:AnyObject = self.init() obj.setValuesForKeysWithDictionary(dict) return obj
      } required override init() {
      super.init()
      }
    • AppInfoCell.swift

      	class AppInfoCell: UITableViewCell	
      
      	@IBOutlet weak var iconView: UIImageView!
      @IBOutlet weak var nameLabel: UILabel!
      @IBOutlet weak var downLabel: UILabel!
    • ViewController.swift

      	// 懒加载
      
          	lazy var dataSourceArray:[AnyObject] = {
      var tmp:[AnyObject] = AppInfoModel.loadPList()
      return tmp
      }() // 表格视图数据源方法 override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
      return self.dataSourceArray.count
      } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell:AppInfoCell = tableView.dequeueReusableCellWithIdentifier("AppCell", forIndexPath: indexPath) as! AppInfoCell let dataModel = self.dataSourceArray[indexPath.row] as! AppInfoModel cell.nameLabel.text = dataModel.name
      cell.downLabel.text = dataModel.download // 设置图片
      cell.iconView.sd_setImageWithURL(NSURL(string: dataModel.icon)) return cell
      }