一、实现一个简单的tableView显示效果
实现效果展示:
代码示例(使用以前在主控制器中进行业务处理的方式)
1.新建一个项目,让控制器继承自UITableViewController。
3.根据plist文件,字典转模型
LYApp.m文件
主控制器中得逻辑控制部分,LYViewController.m文件
问题1:需要保证一个url对应一个operation对象。
问题2:下载完需要移除。移除执行完毕的操作。
问题3:保证一个url对应一个image。
下面对主控制器中得代码进行改进
实现效果展示:
代码示例(使用以前在主控制器中进行业务处理的方式)
1.新建一个项目,让控制器继承自UITableViewController。
#import <UIKit/UIKit.h>
@interface LYViewController : UITableViewController
@end
2.处理Main.storyboard中的界面,如下:
3.根据plist文件,字典转模型
新建一个类,继承自NSObject,作为数据的模型
LYApp.h文件
#import <Foundation/Foundation.h>
@interface LYApp : NSObject
/**应用名称*/
@property (nonatomic, strong) NSString *name;
/**应用图片*/
@property (nonatomic, strong) NSString *icon;
/**应用下载量*/
@property (nonatomic, strong) NSString *download;
- (instancetype)initWithDict:(NSDictionary *)dict;
+ (instancetype)appWithDict:(NSDictionary *)dict;
@end
LYApp.m文件
#import "LYApp.h"
@implementation LYApp
- (instancetype)initWithDict:(NSDictionary *)dict
{
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dict];
}
return self;
}
// 类工厂方法
+ (instancetype)appWithDict:(NSDictionary *)dict
{
return [[self alloc] initWithDict:dict];
}
@end
主控制器中得逻辑控制部分,LYViewController.m文件
#import "LYViewController.h"
#import "LYApp.h"
@interface LYViewController ()
/**应用信息*/
@property (nonatomic, strong) NSArray *apps;
@end
@implementation LYViewController
#pragma mark - 懒加载数据
- (NSArray *)apps
{
if (!_apps) {
NSArray *arrayDict = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];
// 字典转模型
NSMutableArray *arrayModel = [NSMutableArray array];
for (NSDictionary *dict in arrayDict) {
LYApp *app = [LYApp appWithDict:dict];
[arrayModel addObject:app];
}
_apps = arrayModel;
}
return _apps;
}
- (void)viewDidLoad {
[super viewDidLoad];
}
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.apps.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"app_cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
LYApp *app = self.apps[indexPath.row];
cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;
// 下载图片
NSLog(@"加载图片数据-----%@", [NSThread currentThread]);
NSURL *url = [NSURL URLWithString:app.icon];
NSData *data = [NSData dataWithContentsOfURL:url];
cell.imageView.image = [UIImage imageWithData:data];
NSLog(@"图片加载完成");
return cell;
}
@end
打印查看:
二、自定义NSOperation
说明:上面的下载图片数据部分是一个非常耗时的操作,这个操作任务在主线程完成,会严重的影响到用户体验,造成UI卡的现象。下面通过自定义NSOperation,新开线程,让加载图片的任务异步执行。
1.通过代理
在上面的基础上,新建一个类,让其继承自NSOperation
LYDownLoadOperation.h文件
#import <UIKit/UIKit.h>
#pragma mark - 设置代理和代理方法
@class LYDownLoadOperation;
@protocol LYDownLoadOperationDelegate <NSObject>
@optional
- (void)downLoadOperation:(LYDownLoadOperation *)operation didFishedDownLoad:(UIImage *)image;
@end
@interface LYDownLoadOperation : NSOperation
@property (nonatomic, strong) NSString *url;
@property (nonatomic, strong) NSIndexPath *indexPath;
@property (nonatomic, weak) id <LYDownLoadOperationDelegate> delegate;
@end
LYDownLoadOperation.m文件
#import "LYDownLoadOperation.h"
@implementation LYDownLoadOperation
- (void)main
{
NSURL *url = [NSURL URLWithString:self.url];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
NSLog(@"LYDownLoadOperation---%@", [NSThread currentThread]);
// 图片下载完成, 通知代理
if ([self.delegate respondsToSelector:@selector(downLoadOperation:didFishedDownLoad:)]) {
// 回到主线程,传递数据给代理对象
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self.delegate downLoadOperation:self didFishedDownLoad:image];
}];
}
}
@end
主控制器中的业务逻辑:
#import "LYViewController.h"
#import "LYApp.h"
#import "LYDownLoadOperation.h"
@interface LYViewController () <LYDownLoadOperationDelegate>
/**应用信息*/
@property (nonatomic, strong) NSArray *apps;
/**队列*/
@property (nonatomic, strong) NSOperationQueue *queue;
@end
@implementation LYViewController
#pragma mark - 懒加载数据
- (NSArray *)apps
{
if (!_apps) {
NSArray *arrayDict = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];
// 字典转模型
NSMutableArray *arrayModel = [NSMutableArray array];
for (NSDictionary *dict in arrayDict) {
LYApp *app = [LYApp appWithDict:dict];
[arrayModel addObject:app];
}
_apps = arrayModel;
}
return _apps;
}
- (NSOperationQueue *)queue
{
if (!_queue) {
_queue = [[NSOperationQueue alloc] init];
// 设置最大并发数为3
_queue.maxConcurrentOperationCount = 3;
}
return _queue;
}
- (void)viewDidLoad {
[super viewDidLoad];
}
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.apps.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"app_cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
LYApp *app = self.apps[indexPath.row];
cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;
// 下载图片
// NSLog(@"加载图片数据-----%@", [NSThread currentThread]);
// NSURL *url = [NSURL URLWithString:app.icon];
// NSData *data = [NSData dataWithContentsOfURL:url];
// cell.imageView.image = [UIImage imageWithData:data];
// 创建一个Operation对象
LYDownLoadOperation *operation = [[LYDownLoadOperation alloc] init];
operation.url = app.icon;
operation.indexPath = indexPath;
operation.delegate = self;
// 将操作对象添加到队列中
[self.queue addOperation:operation];
// NSLog(@"图片加载完成");
return cell;
}
- (void)downLoadOperation:(LYDownLoadOperation *)operation didFishedDownLoad:(UIImage *)image
{
// 返回图片数据给每一行对应的Cell的imageView.image
// 取出tableView中indexPat这行对应的cell
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:operation.indexPath];
// 显示图片
cell.imageView.image = image;
// 刷新表格数据
[self.tableView reloadRowsAtIndexPaths:@[operation.indexPath] withRowAnimation:UITableViewRowAnimationFade];
NSLog(@"downLoadOperation-----%@", [NSThread currentThread]);
}
@end
说明:通过打印可以发现上面的代码存在很大的问题。
问题1:需要保证一个url对应一个operation对象。
问题2:下载完需要移除。移除执行完毕的操作。
问题3:保证一个url对应一个image。
下面对主控制器中得代码进行改进
#import "LYViewController.h"
#import "LYApp.h"
#import "LYDownLoadOperation.h"
@interface LYViewController () <LYDownLoadOperationDelegate>
/**应用信息*/
@property (nonatomic, strong) NSArray *apps;
/**队列*/
@property (nonatomic, strong) NSOperationQueue *queue;
/**缓存操作*/
@property (nonatomic, strong) NSMutableDictionary *operations;
/**缓存图片*/
@property (nonatomic, strong) NSMutableDictionary *images;
@end
@implementation LYViewController
#pragma mark - 懒加载数据
- (NSArray *)apps
{
if (!_apps) {
NSArray *arrayDict = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"apps.plist" ofType:nil]];
// 字典转模型
NSMutableArray *arrayModel = [NSMutableArray array];
for (NSDictionary *dict in arrayDict) {
LYApp *app = [LYApp appWithDict:dict];
[arrayModel addObject:app];
}
_apps = arrayModel;
}
return _apps;
}
- (NSOperationQueue *)queue
{
if (!_queue) {
_queue = [[NSOperationQueue alloc] init];
// 设置最大并发数为3
_queue.maxConcurrentOperationCount = 3;
}
return _queue;
}
- (NSMutableDictionary *)operations
{
if (!_operations) {
_operations = [NSMutableDictionary dictionary];
}
return _operations;
}
- (NSMutableDictionary *)images
{
if (!_images) {
_images = [NSMutableDictionary dictionary];
}
return _images;
}
#pragma mark - Table view data source
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.apps.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID = @"app_cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
LYApp *app = self.apps[indexPath.row];
cell.textLabel.text = app.name;
cell.detailTextLabel.text = app.download;
// 保证一个url对应一个image对象
UIImage *image = self.images[app.icon];
// 判断缓存中是否有这张图片
if (image) {
cell.imageView.image = image;
}else{
// 先设置一张占位图片
cell.imageView.image = [UIImage imageNamed:@"loading"];
LYDownLoadOperation *operation = self.operations[app.icon];
// 判断是否真正下载
if (!operation) {
// 创建下载操作
operation = [[LYDownLoadOperation alloc] init];
operation.url = app.icon;
operation.indexPath = indexPath;
operation.delegate = self;
// 异步下载
[self.queue addOperation:operation];
self.operations[app.icon] = operation;
}
}
// 将操作对象添加到队列中
// [self.queue addOperation:operation];
// NSLog(@"图片加载完成");
return cell;
}
- (void)downLoadOperation:(LYDownLoadOperation *)operation didFishedDownLoad:(UIImage *)image
{
// 返回图片数据给每一行对应的Cell的imageView.image
// 取出tableView中indexPat这行对应的cell
// UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:operation.indexPath];
// 显示图片
// cell.imageView.image = image;
// 移除执行完成的操作
[self.operations removeObjectForKey:operation.url];
// 将图片添加到缓存中去
self.images[operation.url] = image;
// 刷新表格数据
[self.tableView reloadRowsAtIndexPaths:@[operation.indexPath] withRowAnimation:UITableViewRowAnimationFade];
// NSLog(@"downLoadOperation-----%@", [NSThread currentThread]);
NSLog(@"downLoadOperation---%zd-----%@", operation.indexPath.row, [NSThread currentThread]);
}
// 内存警告
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
self.images = nil;
self.operations = nil;
[self.queue cancelAllOperations];
}
@end
打印查看:
注意:在iOS9 beta中,苹果将原http协议改成了https协议,使用 TLS1.2 SSL加密请求数据,所以不能直接使用http协议访问网络资源,需要在info.plist 加入key
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>