iOS - ImageCache 网络图片缓存

1、ImageCache

  • 使用内存缓存方式:

    ImageCache1

  • 使用沙盒缓存方式:

    ImageCache2

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

    • 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
              }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值