4.5 NSOperation->4.0 自定义NSOperation

本文并非最终版本,如果想要关注更新或更正的内容请关注文集,联系方式详见文末,如有疏忽和遗漏,欢迎指正。


本文相关目录:
==================== 所属文集:4.0 多线程 ====================
4.1 多线程基础->1.0 进程 & 线程
······················ 2.0 多线程简介
4.2 pthread
4.3 NSThread->1.0 创建线程
····················· 2.0 线程属性
····················· 3.0 线程状态/线程生命周期
····················· 4.0 多线程安全隐患
····················· 5.0 线程间通讯和常用方法
4.4 GCD->1.0 GCD简介和使用
·············· 2.0 线程间的通信
·············· 3.0 其他用法
·············· 4.0 GCD 的定时器事件
4.5 NSOperation->1.0 NSOperation简介
························ 2.0 NSOperationQueue
························ 3.0 线程间通信
························ 4.0 自定义NSOperation
4.6 RunLoop - 运行循环
===================== 所属文集:4.0 多线程 =====================


1.0 自定义NSOperation

自定义NSOperation的步骤很简单:
  • 重写- (void)main方法,在里面实现想执行的任务
重写- (void)main方法的注意点:
  • 自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
  • 经常通过- (BOOL)isCancelled方法检测操作是否被取消,对取消做出响应

代码示例:

ViewController.m

#import "TDOperation.h"
#import "ViewController.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

  // 1.创建队列
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];

  // 2.创建自定义 TDGOperation
  TDOperation *op = [[TDOperation alloc] init];

  // 3.把操作(任务)添加到队列中,并自动调用 start 方法
  [queue addOperation:op];
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}
@end

TDOperation.h(继承自:NSOperation)

#import 

@interface TDOperation : NSOperation
@end

TDOperation.m

#import "TDOperation.h"

@implementation TDOperation
//需要执行的任务
- (void)main {
  for (NSInteger i = 0; i < 3; i++) {
    NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
  }
  // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
  if (self.isCancelled)
    return;

  for (NSInteger i = 0; i < 3; i++) {
    NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
  }
  // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
  if (self.isCancelled)
    return;

  for (NSInteger i = 0; i < 3; i++) {
    NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
  }
  // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
  if (self.isCancelled)
    return;
}
@end

运行结果:

自定义NSOperation[1567:84075] download1 -0-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download1 -1-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download1 -2-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download2 -0-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download2 -1-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download2 -2-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download3 -0-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download3 -1-- {number = 2, name = (null)}
自定义NSOperation[1567:84075] download3 -2-- {number = 2, name = (null)}

2.0 自定义NSOperation队列的取消操作

代码示例:

ViewController.m

#import "TDOperation.h"
#import "ViewController.h"

@interface ViewController ()
@property(nonatomic, strong) NSOperationQueue *queue; //队列
@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];

  // 1.创建队列
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];

  // 2.设置最大并发操作数(大并发操作数 = 1,就变成了串行队列)
  queue.maxConcurrentOperationCount = 2;

  // 3.添加操作 - 自定义 NSOperation
  [queue addOperation:[[TDOperation alloc] init]];

  self.queue = queue;
}

#pragma mark - 取消队列的所有操作
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  // 取消队列的所有操作(相等于调用了所有NSOperation的-(void)cancel方法)
  [self.queue cancelAllOperations];
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

@end

TDOperation.h

#import 

@interface TDOperation : NSOperation

@end

TDOperation.m

#import "TDOperation.h"

@implementation TDOperation
//需要执行的任务
- (void)main {
  for (NSInteger i = 0; i < 1000; i++) {
    NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
  }
  // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
  if (self.isCancelled)
    return;

  for (NSInteger i = 0; i < 1000; i++) {
    NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
  }
  // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
  if (self.isCancelled)
    return;

  for (NSInteger i = 0; i < 1000; i++) {
    NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
  }
  // 人为的判断是否执行取消操作,如果执行取消操作,就直接 return 不往下执行
  if (self.isCancelled)
    return;
}
@end

3.0 多图下载

沙盒结构:
Documents
 Library
    - Caches
    - Preference
 tmp
自定义NSOperation下载图片思路 – 有沙盒缓存
4.5 NSOperation->4.0 自定义NSOperation_第1张图片

代码示例:

ViewController.m

#import "TDApp.h"
#import "ViewController.h"

@interface ViewController ()
@property(nonatomic, strong) NSArray *apps;                   //所有数据
@property(nonatomic, strong) NSMutableDictionary *imageCache; //内存缓存的图片
@property(nonatomic, strong) NSOperationQueue *queue;         //队列对象
@property(nonatomic, strong) NSMutableDictionary *operations; //所有的操作对象

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

#pragma mark - 数据源方法
- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section {
  return self.apps.count;
}

#pragma mark - Cell
- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {

  // 重用标识
  static NSString *ID = @"app";
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

  TDApp *app = self.apps[indexPath.row];

#pragma mark - app 名称
  cell.textLabel.text = app.name;

#pragma mark - 下载量
  cell.detailTextLabel.text = app.download;

#pragma mark - 图片
  // 1.先从内存缓存中取出图片
  UIImage *image = self.imageCache[app.icon];

  // 2.判断内存中是否有图片
  if (image) {
    // 2.1 内存中有图片,直接设置图片
    cell.imageView.image = image;
  } else {
    // 2.2 内存中没有图片,将图片文件数据写入沙盒中

    //(1)获得Library/Caches文件夹
    NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(
        NSCachesDirectory, NSUserDomainMask, YES) firstObject];
    //(2)获得文件名
    NSString *filename = [app.icon lastPathComponent];
    //(3)计算出文件的全路径
    NSString *file = [cachesPath stringByAppendingPathComponent:filename];
    //(4)加载沙盒的文件数据
    NSData *data = [NSData dataWithContentsOfFile:file];

    // 2.3 判断沙盒中是否有图片
    if (data) {

      // 有图片,直接利用沙盒中图片,设置图片
      UIImage *image = [UIImage imageWithData:data];
      cell.imageView.image = image;
      // 并将图片存到字典中
      self.imageCache[app.icon] = image;

    } else {

      // 没有图片,先设置一个占位图
      cell.imageView.image = [UIImage imageNamed:@"placeholder"];

      // 取出图片,并判断这张图片是否有下载操作
      NSOperation *operation = self.operations[app.icon];
      if (operation == nil) {
        // 如果这张图片暂时没有下载操作,则需要创建一个下载操作
        // 下载图片是耗时操作,放到子线程
        operation = [NSBlockOperation blockOperationWithBlock:^{
          // 下载图片
          NSData *data =
              [NSData dataWithContentsOfURL:[NSURL URLWithString:app.icon]];
          // 如果数据下载失败
          if (data == nil) {
            // 下载失败,移除操作
            [self.operations removeObjectForKey:app.icon];
            return;
          }

            // 下载成功,将图片放在 image 中
          UIImage *image = [UIImage imageWithData:data];
          // 存到字典中
          self.imageCache[app.icon] = image;

          //回到主线程显示图片
          [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [tableView reloadRowsAtIndexPaths:@[ indexPath ]
                             withRowAnimation:UITableViewRowAnimationNone];
          }];

          // 将图片文件数据写入沙盒中
          [data writeToFile:file atomically:YES];
          // 下载完毕,移除操作
          [self.operations removeObjectForKey:app.icon];
        }];

        // 添加到队列中(队列的操作不需要移除,会自动移除)
        [self.queue addOperation:operation];
        // 并将图片存到字典中
        self.operations[app.icon] = operation;
      }
    }
  }

  return cell;
}

#pragma mark - 数据懒加载
- (NSArray *)apps {
  if (!_apps) {
    NSArray *dictArray =
        [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]
                                             pathForResource:@"apps.plist"
                                                      ofType:nil]];

    NSMutableArray *appArray = [NSMutableArray array];
    for (NSDictionary *dict in dictArray) {
      [appArray addObject:[TDApp appWithDict:dict]];
    }
    _apps = appArray;
  }
  return _apps;
}

#pragma mark - 懒加载
- (NSMutableDictionary *)imageCache {
  if (!_imageCache) {
    _imageCache = [NSMutableDictionary dictionary];
  }
  return _imageCache;
}

#pragma mark - 懒加载
- (NSOperationQueue *)queue {
  if (!_queue) {
    _queue = [[NSOperationQueue alloc] init];
    _queue.maxConcurrentOperationCount = 3;
  }
  return _queue;
}

#pragma mark - 懒加载
- (NSMutableDictionary *)operations {
  if (!_operations) {
    _operations = [NSMutableDictionary dictionary];
  }
  return _operations;
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

@end

TDApp.h

#import 

@interface TDApp : NSObject

@property(nonatomic, strong) NSString *icon;     // 图片
@property(nonatomic, strong) NSString *download; //下载量
@property(nonatomic, strong) NSString *name;     // 名字

+ (instancetype)appWithDict:(NSDictionary *)dict;

@end

TDApp.m

#import "TDApp.h"

@implementation TDApp

+ (instancetype)appWithDict:(NSDictionary *)dict {
  TDApp *app = [[self alloc] init];
  [app setValuesForKeysWithDictionary:dict];
  return app;
}

@end

4.0 多图下载 - SDWebImage

SDWebImage:
  • iOS中著名的网络图片处理框架
  • 包含的功能:图片下载、图片缓存、下载进度监听、gif处理等等
  • 框架地址:https://github.com/rs/SDWebImage
  • SDWebImage的图片缓存周期是:1周

代码示例:

ViewController.m

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

@interface ViewController ()
@property(nonatomic, strong) NSArray *apps;                   //所有数据
@property(nonatomic, strong) NSMutableDictionary *imageCache; //内存缓存的图片
@property(nonatomic, strong) NSOperationQueue *queue;         //队列对象
@property(nonatomic, strong) NSMutableDictionary *operations; //所有的操作对象

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

#pragma mark - 数据源方法
- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section {
  return self.apps.count;
}

#pragma mark - Cell
- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {

  // 重用标识
  static NSString *ID = @"app";
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

  TDApp *app = self.apps[indexPath.row];

#pragma mark - app 名称
  cell.textLabel.text = app.name;

#pragma mark - 下载量
  cell.detailTextLabel.text = app.download;

#pragma mark - 图片
  // expectedSize: 图片的总字节数  receivedSize: 已经接收的图片字节数
  [cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon]
      placeholderImage:[UIImage imageNamed:@"placeholder"]
      options:0 // 0 表示什么都不做
      progress:^(NSInteger receivedSize, NSInteger expectedSize) {

        NSLog(@"下载进度:%f", 1.0 * receivedSize / expectedSize);
      }
      completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType,
                  NSURL *imageURL) {
        NSLog(@"下载完图片");
      }];
  return cell;
}

#pragma mark - 数据懒加载
- (NSArray *)apps {
  if (!_apps) {
    NSArray *dictArray =
        [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]
                                             pathForResource:@"apps.plist"
                                                      ofType:nil]];

    NSMutableArray *appArray = [NSMutableArray array];
    for (NSDictionary *dict in dictArray) {
      [appArray addObject:[TDApp appWithDict:dict]];
    }
    _apps = appArray;
  }
  return _apps;
}

#pragma mark - 懒加载
- (NSMutableDictionary *)imageCache {
  if (!_imageCache) {
    _imageCache = [NSMutableDictionary dictionary];
  }
  return _imageCache;
}

#pragma mark - 懒加载
- (NSOperationQueue *)queue {
  if (!_queue) {
    _queue = [[NSOperationQueue alloc] init];
    _queue.maxConcurrentOperationCount = 3;
  }
  return _queue;
}

#pragma mark - 懒加载
- (NSMutableDictionary *)operations {
  if (!_operations) {
    _operations = [NSMutableDictionary dictionary];
  }
  return _operations;
}

#pragma mark - 设置控制器的内存警告
- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];

  self.imageCache = nil;
  self.operations = nil;
  [self.queue cancelAllOperations];
}

@end

TDApp.h

#import 

@interface TDApp : NSObject

@property(nonatomic, strong) NSString *icon;     // 图片
@property(nonatomic, strong) NSString *download; //下载量
@property(nonatomic, strong) NSString *name;     // 名字

+ (instancetype)appWithDict:(NSDictionary *)dict;

@end

TDApp.m

#import "TDApp.h"

@implementation TDApp

+ (instancetype)appWithDict:(NSDictionary *)dict {
  TDApp *app = [[self alloc] init];
  [app setValuesForKeysWithDictionary:dict];
  return app;
}

@end

5.0【区别】GCD & NSOperationQueue 队列类型的创建方式

GCD 队列类型的创建方式:
(1)并发队列:手动创建、全局
(2)串行队列:手动创建、主队列
NSOperationQueue的队列类型的创建方法:
(1)主队列:[NSOperationQueue mainQueue]
- 凡是添加到主队列中的任务(NSOperation),都会放到主线程中执行

(2)其他队列(同时包含了串行、并发功能):[NSOperationQueue alloc]init]
- 添加到这种队列中的任务(NSOperation),就会自动放到子线程中执行

本文源码 Demo 详见 Github
https://github.com/shorfng/iOS_4.0_multithreading.git





作者:蓝田(Loto)
出处:

如果你觉得本篇文章对你有所帮助,请点击文章末尾下方“喜欢”
如有疑问,请通过以下方式交流:
评论区回复微信(加好友请注明“+称呼”)发送邮件[email protected]



本文版权归作者和本网站共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

你可能感兴趣的:(4.5 NSOperation->4.0 自定义NSOperation)