美文网首页
UIImageView在线图片加载以及缓存优化

UIImageView在线图片加载以及缓存优化

作者: lele8446 | 来源:发表于2016-01-14 21:54 被阅读1761次

    UIImageView是iOS开发中经常用到的控件,通过UIImageView来加载显示网络图片更是很常遇到的场景之一。如何加载显示网络图片,提高用户体验度,我们可以通过多种途径来实现:苹果原生api,SDWebImage,其他第三方库等,这里我结合NSCache以及磁盘缓存实现了一个自己封装的UIImageView库。


    直接说重点,一个好的图片缓存库大概应该包含以下几点:

    1. 异步判断是否有缓存,有就解压缩图片,返回主线程刷新UI(不卡主线程,提高用户体验)。
    2. 无缓存,异步下载图像,尽可能减少使用主线程队列。
    3. 下载成功,后台解压缩图像,返回主线程刷新UI;同时在内存和磁盘上缓存图像,并且保存解压过的图片到内存中,以避免再次解压。
    4. 可使用GCD 和 blocks,使得代码更加高效和简单。
    5. 如果可以,最好在下载后以及存入到缓存前对图像进行处理(裁剪、压缩、拉伸等)

    • 缓存类的实现
      包括内存缓存和磁盘缓存,内存缓存通过NSCache实现。NSCache 是苹果官方提供的缓存类,用法与 NSMutableDictionary 的用法很相似, 在收到系统内存警告时,NSCache会自动释放一些对象,它是线程安全的,在多线程操作中,不需要对NSCache加锁,另外NSCache的Key只是做强引用,不需要实现NSCopying协议。
      // 初始化内存缓存 NSCache
      _memCache = [[NSCache alloc] init];
      //设置10兆的内存缓存作为二级缓存,先读内存,内存没有再读文件
      _memCache.totalCostLimit = 1024102410;
      totalCostLimit表示缓存空间的最大总成本,超出上限会自动回收对象(默认值是 0,表示没有限制)。
      从内存读取图片缓存:
      - (UIImage *)getImageFromMemoryCache:(NSString *)uri
      {
      UIImage *result = nil;
      result = [_memCache objectForKey:uri];
      return result;
      }
      存入内存缓存:
      - (void)saveMemoryCache:(NSData )data uri:(NSString )uri decoded:(BOOL)decoded
      {
      if (data==nil ||uri==nil){
      return;
      }
      //大于一兆的数据就不要放进内存缓存了,不然内存紧张会崩溃)
      if (data.length >1024
      1024
      1){
      return;
      }

           //对于jpg,png图片,将data转为UIImage再存到内存缓存,不用每次获时再执行imageWithData这个非常耗时的操作。
           if (data!=nil){
                @try {
                          UIImage *image=[UIImage imageWithData:data];
                          UIImage *resultImage = nil;
                          //png图片不执行decoded
                          if (decoded && ![CJImageViewCache isPNGImage:data]) {
                              resultImage = [CJImageViewCache decodedImageWithImage:image];
                          }
                          if (nil == resultImage) {
                              resultImage = image;
                          }
                          NSInteger dataSize= resultImage.size.width * resultImage.size.height * resultImage.scale;
                          [_memCache setObject:resultImage forKey:uri cost:dataSize];
                }
                @catch (NSException *exception) {
                          NSLog(@"exception=%@",exception);
                }
            }
      }
      

    这里说一下,在图片存入内存前预先进行decodedImageWithImage:可以提高图片的显示效率,但是经测试发现如果图片是.png格式时,是无法decoded的,所以这里做多了一个判读,当图片是png格式时则忽略decoded。关于如何判断图片,我们可以通过根据文件头来判断图片是否为png格式。

    • 磁盘缓存
      从磁盘读取缓存
      - (UIImage *)getImageFromDiskCache:(NSString *)uri decoded:(BOOL)decoded
      {
      UIImage *image = nil;
      NSData *data = [NSData dataWithContentsOfFile:[self cachePath:uri]];
      if (data) {
      image = [UIImage imageWithData:data];
      //png图片不执行decoded
      if (decoded && ![CJImageViewCache isPNGImage:data]){
      image = [CJImageViewCache decodedImageWithImage:image];
      }
      if (image) {
      CGFloat cost = image.size.height * image.size.width * image.scale;
      [self.memCache setObject:image forKey:uri cost:cost];
      }
      }
      return image;
      }
      缓存路径的判断:
      - (NSString *)cachePath:(NSString *)relaPath
      {
      // nil 安全判断
      if (relaPath != nil && relaPath.length > 0) {
      const char *str = [relaPath UTF8String];
      if (str == NULL) {
      str = "";
      }
      unsigned char r[16];
      CC_MD5(str, (CC_LONG)strlen(str), r);
      NSString *filename = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
      r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15]];
      return [_diskCachePath stringByAppendingPathComponent:filename];
      }
      return nil;
      }
      其中_diskCachePath为图片缓存路径(fullNameSpace是自定义字符串)
      //图片缓存路径
      _diskCachePath = [paths[0] stringByAppendingPathComponent:fullNamespace];
      将数据写入磁盘缓存
      - (void)saveDiskCache:(NSData *)imageData uri:(NSString *)uri
      {
      UIImage *image = [UIImage imageWithData:imageData];
      NSData *data = imageData;
      if ([CJImageViewCache isPNGImage:imageData]) {
      data = UIImagePNGRepresentation(image);
      }
      else {
      data = UIImageJPEGRepresentation(image, (CGFloat)1.0);
      }

          if (data) {
              @synchronized(_fileManager) {
                  if (![_fileManager fileExistsAtPath:_diskCachePath]) {
                      [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
                  }
                  if ([_fileManager fileExistsAtPath:[self cachePath:uri]]) {
                      [_fileManager removeItemAtPath:[self cachePath:uri] error:nil];
                  }
                  [_fileManager createFileAtPath:[self cachePath:uri] contents:data attributes:nil];
      //            NSLog(@"_diskCachePath = %@",_diskCachePath);
              }
          }
      }
      

    _fileManager在初始化时实现
    _fileManager = [NSFileManager defaultManager];
    另外缓存类还实现了清除缓存,清除指定缓存,获取缓存大小等方法,这里就不再细说了,大家可以下载demo 看看。


    • CJImageView图片加载类
      图片加载过程:判断是否有缓存;有缓存直接显示缓存图片;无缓存先显示默认图片,开始请求网络,加载期间可选是否显示加载菊花,加载成功停止loading动画,显示网络图片,并保存缓存。
      /**
      * 加载图片,设置默认图,显示加载菊花,设置加载菊花样式,图片是否decoded
      *
      * @param uri
      * @param image
      * @param showIndicator
      * @param style 默认UIActivityIndicatorViewStyleGray
      * @param decoded 是否decoded
      */
      - (void)setUri:(NSString *)uri defaultImage:(UIImage *)image showIndicator:(BOOL)showIndicator style:(UIActivityIndicatorViewStyle)style decoded:(BOOL)decoded
      {
      __weak __typeof(self) wSelf = self;
      dispatch_async([self getImageOperatorQueue], ^(){
      UIImage * resultImage = [[CJImageViewCache sharedImageCache]getImageFromCache:uri decoded:decoded];
      if (resultImage != nil) {
      dispatch_async_main_queue(^{
      wSelf.image = resultImage;
      });
      }else{
      //获取缓存失败,请求网络
      [wSelf loadImageData:uri defaultImage:image showIndicator:showIndicator style:style decoded:decoded];
      }
      });
      }
      请求网络使用了我之前发表的CJHttpClient库,这里由于CJImageView实现了自己的网络缓存,所以网络请求时使用忽略缓存协议:CJRequestIgnoringLocalCacheData
      - (void)loadImageData:(NSString *)uri defaultImage:(UIImage *)image showIndicator:(BOOL)showIndicator style:(UIActivityIndicatorViewStyle)style decoded:(BOOL)decoded
      {
      self.style = style;
      self.url = uri;
      __weak typeof(self) wSelf = self;
      //先显示默认图片
      dispatch_async_main_queue(^{
      wSelf.image = image;
      });

          if (showIndicator) {
              if (!_loadingInducator) {
                  UIActivityIndicatorView *tempIndicator =  [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:self.style];
                  self.loadingInducator = tempIndicator;
            
                  CGFloat minFloat = MIN(self.frame.size.width, self.frame.size.height);
                  CGFloat inducatorMaxFloat = MAX(tempIndicator.frame.size.width, tempIndicator.frame.size.height);
                  if (minFloat/inducatorMaxFloat < 2) {
                      self.loadingInducator.transform = CGAffineTransformScale(self.loadingInducator.transform, 0.6, 0.6);
                  }
              }
              self.loadingInducator.activityIndicatorViewStyle = self.style;
              self.loadingInducator.center = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
              dispatch_async_main_queue(^{
                  [wSelf addSubview:wSelf.loadingInducator];
                  [wSelf bringSubviewToFront:wSelf.loadingInducator];
                  [wSelf.loadingInducator startAnimating];
              });
           }
      
        [CJHttpClient getUrl:uri parameters:nil timeoutInterval:HTTP_DEFAULT_TIMEOUT cachPolicy:CJRequestIgnoringLocalCacheData completionHandler:^(NSData *data, NSURLResponse *response){
            __strong __typeof(wSelf)strongSelf = wSelf;
            //保存缓存
            [[CJImageViewCache sharedImageCache] saveCache:data uri:uri decoded:decoded];
            dispatch_async([self getImageOperatorQueue], ^(){
                if ([[response.URL absoluteString] isEqualToString:self.url]) {
                    UIImage * dataImage = [UIImage imageWithData:data];
                    UIImage * resultImage = nil;
                    if (decoded && ![CJImageViewCache isPNGImage:data]) {
                        resultImage = [CJImageViewCache decodedImageWithImage:dataImage];
                    }
                    dispatch_async_main_queue(^{
                        strongSelf.image = resultImage != nil?resultImage:(dataImage != nil?dataImage:image);
                        [strongSelf.loadingInducator stopAnimating];
                        [strongSelf sendSubviewToBack:strongSelf.loadingInducator];
                    });
                 }
            });
        }errorHandler:^(NSError *error){
            __strong __typeof(wSelf)strongSelf = wSelf;
            dispatch_async_main_queue(^{
                strongSelf.image = image;
                [strongSelf.loadingInducator stopAnimating];
                [strongSelf sendSubviewToBack:strongSelf.loadingInducator];
            });
        }];
      }
      

    最后是demo地址。

    相关文章

      网友评论

          本文标题:UIImageView在线图片加载以及缓存优化

          本文链接:https://www.haomeiwen.com/subject/bpickttx.html