UIImageView在線圖片加載以及緩存優化

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地址。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容