iOS开发多线程-自定义NSOperation

一、实现一个简单的tableView显示效果

实现效果展示:

代码示例(使用以前在主控制器中进行业务处理的方式)
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> 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值