一般咱们看到的tableView几乎都是图文混排只展现文字的特别少,tableView下载图片和文字的过程仍是比较复杂的,下面请听我一一道来。javascript
要从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加载图片就讲完了。