UITableView介紹 之 網絡圖片數據加載

UITableView網絡圖片數據加載

  一般咱們看到的tableView幾乎都是圖文混排只展現文字的特別少,tableView下載圖片和文字的過程仍是比較複雜的,下面請聽我一一道來。javascript

搭建簡單的web後臺

  要從web獲取數據這裏簡單用Node.js搭建一個後,有興趣的能夠跟着作一下,只是爲了演示,因此後臺的業務邏輯也比較簡單,廢話很少說直接上代碼:java

const PORT = 8000;
const http = require('http');
const url = require('url');
const fs = require('fs');
const mime = require('./mime').types;
const path = require('path');
const dataJson = require('./dataJson').dataJson;
const querystring = require('querystring');

// 開啓一個簡單web服務器
var server = http.createServer((req, resp) => {
    var murl = url.parse(req.url);
    var pathName = murl.pathname;

    if (pathName.indexOf('webServer')) {
        // 進行簡單的路由
        route(murl, resp);

    } else {
        resp.end(errorMessage());
    }

}).listen(PORT);

console.log('server running ....');


function route (murl, resp) {
    var pathName = murl.pathname;
    // 返回數據處理
    if (pathName == '/webServer' || pathName == '/webServer/') {
        reJsonMessage(resp);
    } else {    // 返回圖片
        var pArr = pathName.split('/webServer');
        var realPath = 'assert' + pArr[pArr.length - 1];

        fs.exists(realPath, (exists) => {
            if (exists) {

                readFile(realPath, resp);

            } else {
                resp.end(errorMessage());
            }
        });
    }

}

// 根據不一樣圖片的mime選擇性返回
function readFile(filePath, resp) {
    fs.readFile(filePath, (err, data) => {
        if (err) {
            resp.end(errorMessage());
        } else {
            var ext = path.extname(filePath);
            ext = ext ? ext.slice(1) : 'unknown';
            var contentType = mime[ext] || 'text/plain';
            resp.writeHead(200, {'Content-Type': contentType});
            resp.end(data);
        }
    });
}


function errorMessage () {
    var redata = {};
    redata.flag = 'fail';
    redata.value = 'access error!';

    return JSON.stringify(redata);
}

function reJsonMessage (resp) {
    resp.writeHead(200, {'Content-Type': mime.json + ';charset=utf-8'});

    var redata = {};
    redata.flag = 'success';
    redata.value = dataJson;
    resp.end(JSON.stringify(redata), 'utf8');
}

完整代碼的下載路徑 使用方法很簡單在控制檯運行 node index.js 便可,前提是你的機器已安裝最新版 node,注意:dataJson.js中的ip改爲和你機器相同,訪問服務器的地址:http://localhost:8000/webServer 便可拿到返回的json數據格式以下:node

{
    "flag": "success",
    "value": [
        {
            "detail": "高德是中國領先的數字地圖內容、導航和位置服務解決方案提供商。",
            "title": "高德地圖",
            "url": "http://192.168.31.167:8000/webServer/autonavi_minimap_72px_1127483_easyicon.net.png"
        },
        {
            "detail": "百度是全球最大的中文搜索引擎、最大的中文網站。",
            "title": "百度",
            "url": "http://192.168.31.167:8000/webServer/bdmobile_android_app_72px_1127470_easyicon.net.png"
        },
        .....
        ]
 }

到此準備工做已經完成,下面開始擼ios代碼。android

加載網絡圖片

簡單粗暴的獲取數據

//
//  ViewController.m
//  UITableViewDemo
//
//  Created by code_xq on 16/3/3.
//  Copyright © 2016年 code_xq. All rights reserved.
//

#import "ViewController.h"
#import "AFNetworking.h"

@interface DataModel : NSObject

/**描述信息*/
@property (nonatomic, copy) NSString *detail;
/**圖片地址*/
@property (nonatomic, copy) NSString *url;
/**標題*/
@property (nonatomic, copy) NSString *title;
/**數據模型轉換方法*/
+ (instancetype)initWithDict:(NSDictionary *)dict;
@end

@implementation DataModel
+ (instancetype)initWithDict:(NSDictionary *)dict {
    DataModel *dm = [[self alloc] init];
    [dm setValuesForKeysWithDictionary:dict];
    return dm;
}
@end


@interface ViewController ()<UITableViewDataSource, UITableViewDelegate>

/**數據源*/
@property (nonatomic, strong) NSMutableArray *dataSource;
/**table引用*/
@property (nonatomic, weak) UITableView *tableView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:tableView];
    
    tableView.dataSource = self;
    tableView.delegate = self;
    self.tableView = tableView;
    
    // 請求數據
    [self getDataFromWeb];
}

/**
 *  請求網絡數據
 */
- (void)getDataFromWeb {
    
    // 爲了防止block中產生循環引用
    __weak typeof (self)weakSelf = self;
    
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    [manager GET:@"http:192.168.31.167:8000/webServer"
          parameters:nil progress:nil 
          success:^(NSURLSessionTask *task, id responseObject) {
        NSDictionary *dict = responseObject;
        
        if([@"success" isEqualToString:dict[@"flag"]]) {
            NSArray *temp = dict[@"value"];

            for (NSDictionary *d in temp) {
                DataModel *dm = [DataModel initWithDict:d];
                [weakSelf.dataSource addObject:dm];
            }
            
            [weakSelf.tableView reloadData];
        }

    } failure:^(NSURLSessionTask *operation, NSError *error) {
        NSLog(@"Error: %@", error);
    }];
}

#pragma mark -  代理方法<UITableViewDataSource>
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return self.dataSource.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    // 定義static變量
    static NSString *const ID = @"cell";
    // 先從緩存中找,若是沒有再建立
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
    }
    DataModel *dm = self.dataSource[indexPath.row];
    
    cell.textLabel.text = dm.title;
    cell.detailTextLabel.text = dm.detail;
    // 圖片下載處理
    [self downLoadCellPic:cell data:dm];
    return cell;
}

/**
 *  下載圖片
 */
- (void)downLoadCellPic:(UITableViewCell *)cell data:(DataModel *)dm {
    NSURL *url = [NSURL URLWithString:dm.url];
    NSData *imgData = [NSData dataWithContentsOfURL:url];
    UIImage *image = [[UIImage alloc] initWithData:imgData];
    cell.imageView.image = image;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
    return 80;
}

/**
 *  數據源懶加載
 */
- (NSMutableArray *)dataSource {
    if (_dataSource == nil) {
        _dataSource = [NSMutableArray array];
    }
    return _dataSource;
}

@end

下載圖片的代碼一眼開上去還行,可是它至少存在這麼幾個問題:在主線程中下載圖片每次滑動都會下載圖片ios

在子線程中下載圖片

上面的下載圖片的方法修改成下面的代碼:git

/**
 *  下載圖片
 */
- (void)downLoadCellPic:(UITableViewCell *)cell data:(DataModel *)dm {
    
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSURL *url = [NSURL URLWithString:dm.url];
        NSData *imgData = [NSData dataWithContentsOfURL:url];
        UIImage *image = [[UIImage alloc] initWithData:imgData];
        // 跟新界面操做放在主線程中
        dispatch_sync(dispatch_get_main_queue(), ^{
            cell.imageView.image = image;
        });
    }];
    
    [self.queue addOperation:operation];
}

這裏我採用了ios中的queue來異步下載圖片,可是上面提到的圖片重複下載屢次的問題依然存在並且在滑動的過程當中會建立不少線程嚴重影響性能github

圖片僅下載一次

NSBlockOperation *operation = self.queueDicts[dm.url];
    if (!operation) {
        operation = [NSBlockOperation blockOperationWithBlock:^{
            NSURL *url = [NSURL URLWithString:dm.url];
            NSData *imgData = [NSData dataWithContentsOfURL:url];
            UIImage *image = [[UIImage alloc] initWithData:imgData];
            // 跟新界面操做放在主線程中
            dispatch_sync(dispatch_get_main_queue(), ^{
                cell.imageView.image = image;
            });
        }];
        
        [self.queue addOperation:operation];
        // 用字典緩存下載隊列
        [self.queueDicts setObject:operation forKey:dm.url];
    }

用一個字典做爲緩存url做爲key來緩存隊列,這樣能夠解決一個url被屢次下載的狀況,可是這樣作又產生了一些其餘問題,因爲cell是重複利用的因此你往下滑一段後,再滑回去發現原來的每行對應的圖片變了,這是由於圖片只能下載一次cell重複利用了下面的圖片。還有一個問題就是若是有圖片下載失敗那就沒辦法解決了,爲了解決問題代碼還需改進web

dispatch_sync(dispatch_get_main_queue(), ^{
      cell.imageView.image = image;
      // 移除操做
      [self.queueDicts removeObjectForKey:dm.url];
  });

在下載完成後將緩存的操做移除,但這樣作又回到了前面一個url被請求屢次的狀況,那麼代碼還須要繼續改進,個人思路是再作一個圖片緩存json

/**
 *  下載圖片
 */
- (void)downLoadCellPic:(UITableViewCell *)cell data:(DataModel *)dm {
    
    UIImage *img = self.picDicts[dm.url];
    if (img) {
        cell.imageView.image = img;
    } else {
        // 添加默認背景圖片,避免剛加載時沒有預留圖片位置
        cell.imageView.image = [UIImage imageNamed:@"background_icon"];
        
        NSBlockOperation *operation = self.queueDicts[dm.url];
        if (!operation) {
            operation = [NSBlockOperation blockOperationWithBlock:^{
                NSURL *url = [NSURL URLWithString:dm.url];
                NSData *imgData = [NSData dataWithContentsOfURL:url];
                UIImage *image = [[UIImage alloc] initWithData:imgData];
                // 跟新界面操做放在主線程中
                dispatch_sync(dispatch_get_main_queue(), ^{
                    // 將圖片放入緩存
                    if (image) {
                        [self.picDicts setObject:image forKey:dm.url];
                    }
                    
                    cell.imageView.image = image;
                    // 移除操做
                    [self.queueDicts removeObjectForKey:dm.url];
                });
            }];
            
            [self.queue addOperation:operation];
            // 用字典緩存下載隊列
            [self.queueDicts setObject:operation forKey:dm.url];
        }
    }
}

通過上面的改造基本上能夠解決一個url被下載屢次的問題,目前測試上面代碼一切正常。網絡極差的狀況下沒有測試過。緩存

經過三方框架進行圖片加載

上面的代碼基本講清楚了圖片加載的這個過程,可是在某些細節方面可能還不那麼完美,可是不用擔憂大名鼎鼎的SDWebImage github地址 已經爲咱們封裝好了圖片下載的整個過程,並且使用很是簡單:

#import "UIImageView+WebCache.h"
....

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    // 定義static變量
    static NSString *const ID = @"cell";
    // 先從緩存中找,若是沒有再建立
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
    }
    DataModel *dm = self.dataSource[indexPath.row];
    
    cell.textLabel.text = dm.title;
    cell.detailTextLabel.text = dm.detail;
    // 圖片下載處理
//    [self downLoadCellPic:cell data:dm];
    [cell.imageView sd_setImageWithURL:[NSURL URLWithString:dm.url]
                      placeholderImage:[UIImage imageNamed:@"background_icon"]];
    return cell;
}

運行效果

引入頭文件,將原來下載的圖片的方法改成上面的形式便可,一個方法就搞定,少了不少繁瑣的過程,到此爲止UITableView加載圖片就講完了。

相關文章
相關標籤/搜索