你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS 【Multithreading-多圖下載數據展示案例(二級緩存)/模擬SDWebImage內部實現】

iOS 【Multithreading-多圖下載數據展示案例(二級緩存)/模擬SDWebImage內部實現】

編輯:IOS開發綜合
#import "ViewController.h"
#import "WZYApp.h"

@interface ViewController ()

// 數據模型數組
@property (nonatomic, strong) NSArray *apps;
// 保存操作對象的字典
@property (nonatomic, strong) NSMutableDictionary *operations;
// 內存緩存
@property (nonatomic, strong) NSMutableDictionary *images;
// 操作隊列(防止重復創建)
@property (nonatomic, strong)  NSOperationQueue *queue;

@end

@implementation ViewController

// 存放操作
-(NSMutableDictionary *)operations
{
    if (_operations == nil) {
        _operations = [NSMutableDictionary dictionary];
    }
    return _operations;
}
-(NSOperationQueue *)queue
{
    if (_queue ==nil) {
        _queue = [[NSOperationQueue alloc]init];
    }
    return _queue;
}
-(NSMutableDictionary *)images
{
    if (_images == nil) {
        _images = [NSMutableDictionary dictionary];
    }
    return _images;
}

-(NSArray *)apps
{
    if (_apps == nil) {
        
        // 加載plist文件
        NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];
        
        // 字典數組 -->模型數組
        NSMutableArray *arrayM = [NSMutableArray array];
        for (NSDictionary *dict in array) {
            [arrayM addObject:[WZYApp appWithDict:dict]];
        }
        
        _apps = arrayM;
    }
    
    return _apps;
}

-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.apps.count;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"---%@", [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]);
    
    //01 創建cell
    static NSString *ID = @"app";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    
    //02 設置cell的數據
    //2.1 得到該行cell對應的數據
    WZYApp *appM = self.apps[indexPath.row];
    //2.2 設置標題
    cell.textLabel.text = appM.name;
    //2.3 設置子標題
    cell.detailTextLabel.text = appM.download;
    //2.4 設置圖片
    
    
    // 內存緩存(指一個字典屬性)思路
    /*
     001 當把圖片下載完成之後需要把該圖片保存到內存緩存
     002 在需要顯示圖片的時候,先檢查本地的緩存中時候已經下載了該圖片
     003 如果緩存中有該圖片,直接設置
     004 如果緩存中沒有改圖片,此時需要去下載圖片
     */
    
    // 磁盤緩存(沙盒Caches下)思路
    /*
     001 當圖片下載完成之後除了保存到內存緩存中之外,還需要保存一份到磁盤緩存中
     002 當圖片需要顯示的時候,先檢查內存緩存,如果內存緩存中有數據那麼就直接設置
     003 如果內存緩存中沒有數據,那麼再去檢查磁盤緩存
     004 如果有數據,那麼就直接拿來設置就可以 | 保存一份到內存緩存中
     005 如果沒有數據,那麼這個時候再去下載數據
    */
    // 改善緩存結構(內存緩存--->二級緩存結構[內存緩存-沙盒緩存])
    UIImage *image = [self.images objectForKey:appM.icon];
    if (image) { // 內存緩存中有數據,就直接設置數據
        cell.imageView.image = image;
        NSLog(@"第%zd行cell對應的圖片已經存在,直接使用內存緩存",indexPath.row);
    } else { // 內存緩存中沒有數據
        // 獲得磁盤緩存路徑(三步)
        NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        NSString *fileName = [appM.icon lastPathComponent]; // 得到url中最後一個節點(從路徑中獲得完整的文件名,帶後綴)
        NSString *fullPath = [caches stringByAppendingPathComponent:fileName]; // 拼接沙盒緩存路徑(將上面兩個字符串拼接)
        
        // 檢查磁盤緩存
        NSData *data = [NSData dataWithContentsOfFile:fullPath];
//        data = nil;
        if (data) { // 磁盤緩存中有數據(模擬二次重啟程序,內存緩存清空了,但是磁盤緩存還在,所以先將數據展示,然後再保存一份數據到內存緩存中)
            //顯示圖片
            UIImage *image = [UIImage imageWithData:data];
            cell.imageView.image = image;
            
            // 保存一份到內存緩存中
            [self.images setObject:image forKey:appM.icon];
            
              NSLog(@"%zd行cell對應的圖片使用了磁盤緩存",indexPath.row);
        } else { // 磁盤緩存無數據(模擬首次進入程序,內存緩存和磁盤緩存都是空的。那麼就先下載數據,然後再顯示數據,接著保存一份數據到內存緩存,最後再保存一份數據到磁盤緩存)
            // 解決數據錯亂問題(由於cell的重用原則,會重用cell及其內部數據)
            // 解決方案001 cell.imageView.image = nil;(但這樣不好,如果網速很卡,用戶會認為沒有圖片存在
            // 解決方案002 設置占位圖片,如下行代碼
            cell.imageView.image = [UIImage imageNamed:@"Snip20160712_43"];

            // 解決圖片重復下載問題(由於用戶可能會不停拖拽界面,當cell重復出現在視野中並且網速較慢的時候,第一次cell進入的時候就已經創建好操作進行下載圖片,但是此時cell若再次進入視野並且首次下載還未執行完,那麼就會進行二次重復下載。)
            // 解決方案:先檢查圖片的下載操作是否已經存在
            // 若 存在 等待就行(攔截二次下載)
            // 若 不存在 封裝操作並且添加到隊列(進行首次下載)
            NSBlockOperation *download = [self.operations objectForKey:appM.icon];
    
            if (download == nil) { // 如果操作不存在
                //封裝操作
                NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{
                    
                    NSURL *url = [NSURL URLWithString:appM.icon];
                    
                    for (NSInteger i = 0; i<1000000000; i++) {
                        //模擬下載該圖片需要花費較長的時間|網絡不好的情況
                    }
                    
                    // 下載操作
                    NSData *data = [NSData dataWithContentsOfURL:url];
                    UIImage *image = [UIImage imageWithData:data];
                    
                    if (image == nil) {
                        // 解決image為空時存到內存緩存報錯問題(如果修改了數據,比如圖片的url修改了,url還在但圖片沒有了,此時如果再執行將image保存到內存緩存(也就是字典中),是會報錯的。因為nil不能往字典中存。)
                        // 解決方案:
                        // 要加一個判定if語句,如果數據不存在,就不要賦值,直接返回
                        
                        // 解決網絡卡頓下載失敗情況下的再次下載問題
                        // 解決方案:
                        // 將操作從緩存中移除(如果在下載的過程中網絡中斷,造成了下載失敗,下載操作已經創建,但是下載任務還沒有執行完畢。此時二次聯網,再次執行下載操作,就不會再繼續下載了。為什麼呢?因為防止圖片重復下載,操作已經創建之後就不會再次創建。那麼這個情況下就要在判定image==nil的if中清空操作。也就是如果image沒有成功設置,就清空下載操作,下次下載時再重新添加操作下載。)
                        [self.operations removeObjectForKey:appM.icon];
                        return ;
                    }
                    
                    // 把圖片保存到內存緩存中
                    [self.images setObject:image forKey:appM.icon];
                    NSLog(@"下載%zd行cell對應的圖片",indexPath.row);
                    
                    // 保存一份到磁盤緩存中
                    [data writeToFile:fullPath atomically:YES];
                    
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                        // 解決圖片不顯示問題(拖動tableView才會顯示。為什麼呢?因為是異步執行,所以說沒有等待cell.imageView.image設置成功就返回cell了。)
                        // 解決方案:
                        // 手動刷新一下cell的當前行,這樣不用拖動也會顯示數據了。
                        //[tableView reloadData]; 刷新整個tableView,耗費內存資源,不推薦
                        [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; // 刷新當前行
                    }];
                }];
                
                // 把操作緩存起來(用一個字典去接收保存操作對象,避免重復創建消耗內存)
                [self.operations setObject:download forKey:appM.icon];
                
                // 添加操作到隊列(執行操作中的內容)
                // 將下載操作保存到子線程中去執行,解決UI卡頓的問題。
                [self.queue addOperation:download];

            } else { // 如果操作不存在
                //等著
                NSLog(@"%zd行對應的圖片已經正在下載,請等待....",indexPath.row);
            }
        }
    }
    
    //03 返回cell
    return cell;
}

以上操作我們完全沒有必要去寫,因為十分繁瑣,而且考慮到的情況還是有限的。我們可以用第三方框架SDWebImage幫我們實現下載圖片二級緩存的操作。該框架內部還處理了很多我們暫時考慮不到的bug。省去了大量繁瑣的工作。上面設置cell.imageView.image的操作共計100余行,我們可以用下面一行代碼搞定:

 

[cell.imageView sd_setImageWithURL:[NSURL URLWithString:appM.icon] placeholderImage:[UIImage imageNamed:@"Snip20160712_43"]];

 

注意一點:

直接用SDWebImage去設置image的時候,如果是在tableView上面設置,那麼會因為imageView的尺寸沒有提前設置而產生一些問題,所以我們需要提前設置好cell.imageView的尺寸。這時就需要自定義cell了。(SDWebImage這個框架是服務很多地方的,並不只是tableView一種,所以說會出現這種bug,而作者也提出了解決方案)

\

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved