小时光上传队列分享——NSOperation的使用

上传队列分享——NSOperation的使用

1. 需求分析

发一条记录的流程(而且能够同时发多条记录)html

img1

其余产品/技术需求(影响到设计因此也加在这)面试

  1. 并发要求——图片确定要支持并发上传、记录串行上传(并行也能够,可是图片已经支持并发了,因此没意义,除非每条记录都是一张照片)。
  2. 任务可取消——用户能够暂停、继续、删除一条任务(影响技术选型)。
  3. 支持杀死app后,启动app继续任务。

2. 设计(上传队列)

无论你在何处工做,构建些什么,用何种编程语言,在软件开发上,一直伴随你的那个不变真理是什么?—— 《Head First 设计模式》编程

A. 年轻时的想法(针对实现设计)

维护两种队列,暂且称为一级队列和二级队列。数据模型的设计和看到的界面(业务)是对应的,思路比较天然。如今我称之为针对实现设计。优缺点咱们后面陈述。设计模式

img2

B. 稍后来的想法(面向抽象设计/分层)

主要分为两层:markdown

  • 上传层(只负责上传图片,有上传单张图片的任务扔过来就好)
  • 日记队列层(面向业务)

img3

二者最主要的区别:多线程

计A每一处设计都是面向业务的,没有抽象;设计B中抽象层是把“上传图片功能”抽象了出来,能够设计的彻底不知道业务(什么叫不知道业务?)并发

B的设计有什么优势?app

  • 易扩充——因为是抽象出来的通用模块,App中其余的上传图片业务(好比更改头像、支持H5上传一张图片)均可以不改代码直接使用,甚至换成别的App也能够直接拿过来使用。
  • 易维护——假如上传图片不使用七牛了,换成了腾讯云,只须要更改上传层的代码就行了,否则的话,可能会改不少地方。

上传层其实还能够抽象出不少层异步

3. 技术实现

Why NSOperation?

最关键是实现上传队列,第一反应确定是考虑GCD或者NSOperation。这里说下最终选择NSOperation的缘由:async

  1. NSOperation自然的OOP,咱们只须要将单个上传的逻辑封装在一个NSOperation便可,而使用GCD,须要额外不少代码来封装。
  2. 须要支持cancel一个任务,因此选择NSOperation会方便些。

NSOperation类介绍

对于NSOperation,须要了解如下几件事:

  1. NSOperation为抽象类,咱们直接使用NSOperation,通常都是自定义NSOperation的子类,或者使用系统提供的两个子类,NSInvocationOperation和NSBlockOperation。(后两个其实都不怎么经常使用,本身查资料了解)。
  2. 两种执行NSOperation的方式:扔到NSOperation Queue里或者调用“start”方法手动触发。
  3. Dependencies,使用NSOperation能够方便的设置任务之间的依赖关系(面试总爱问,我们没有用,其实有个场景想一下以为很合适?)
  4. 若是手动调用start执行一个任务,那么默认任务将会在调用start的线程,同步执行。(isAsynchronous属性)
  5. When you add an operation to an operation queue, the queue ignores the value of the asynchronous property and always calls the start method from a separate thread.

自定义NSOperation子类(今天讲的重点)

1. 使用好处

  1. 自然分离代码到一个类中,不容易形成代码臃肿
  2. 对于子线程里作异步任务,可以比较灵活的处理(每一个NSOperation里执行的依然是一个异步任务、token、七牛上传),换句话讲,可以本身来控制每一个NSOperation的状态。

2. 典型作法

先回忆一下,我们要干什么 —— 用一个队列维护一个或者多个任务,每一个任务就是上传一个图片到七牛

先来解决“每一个任务就是上传一个图片到七牛”这件事,会想到3点要面临的挑战:

  1. 上传这个耗时的任务,放哪,怎么处理。
  2. 怎么得知任务已经完成。
  3. 若是中途取消了怎么处理。

下面经过代码来看下使用NSOperation自定义类,怎么处理这三个问题,代码来自Apple官方文档《Concurrency Programming Guide》,能够对照着看下我们的代码。

@interface MyOperation : NSOperation {
    BOOL        executing;
    BOOL        finished;
}
- (void)completeOperation;
@end
 
@implementation MyOperation
- (id)init {
    self = [super init];
    if (self) {
        executing = NO;
        finished = NO;
    }
    return self;
}
 
- (BOOL)isConcurrent { // ------------2
    return YES;
}
 
- (BOOL)isExecuting { // ------------2
    return executing;
}
 
- (BOOL)isFinished { // ------------2
    return finished;
}

- (void)start { // ------------1
   // Always check for cancellation before launching the task.
   if ([self isCancelled]) // ------------3
   {
      // Must move the operation to the finished state if it is canceled.
      [self willChangeValueForKey:@"isFinished"];
      finished = YES;
      [self didChangeValueForKey:@"isFinished"];
      return;
   }
 
   // If the operation is not canceled, begin executing the task.
   [self willChangeValueForKey:@"isExecuting"];
   [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
   executing = YES;
   [self didChangeValueForKey:@"isExecuting"];
}

- (void)main { // ------------1
   @try {
 
       // Do the main work of the operation here.
 
       [self completeOperation];
   }
   @catch(...) {
      // Do not rethrow exceptions.
   }
}
 
- (void)completeOperation {
    [self willChangeValueForKey:@"isFinished"]; // ------------4
    [self willChangeValueForKey:@"isExecuting"];
 
    executing = NO;
    finished = YES;
 
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}
@end

代码解读

  1. 前面已经说过NSOperation是抽象类了,因此子类确定要实现它的抽象方法,通常须要实现start、main方法。start方法里通常,更改状态,main用来执行主要任务(上传图片放这里!!
  2. 覆写几个状态的getter方法。指定用来记录状态的变量,好比executing、finished,因此系统能够根据NSOperation的状态,进行相应的处理。
  3. 阶段性检查任务是否被cancel,被cancel以后要把executing、finished改成对应的状态值。(这一步很重要)。
  4. 手动触发KVO,告诉系统任务的状态发生了改变。而后系统调用getter方法时,发现isFinished状态变成了YES,则认为这个任务完成,移除NSOperation Queue。

剩余一些细节

  1. 上传七牛以前,须要先经过我们本身API获取token。若是不能本身灵活控制NSOperation的状态,不少步骤都要作出成同步的(自定义NSOperation给咱们更大的灵活性,不管这个任务有多复杂,是同步仍是异步,均可以控制它状态的变化)
  2. 咱们知道,每一个Operation的main方法,确定是会并发运行的,而token的获取其实只要获取一次,就行了,因此,咱们使用了信号量dispatch_semaphore来确保只之执行一次token的请求。

  3. 而后,每张图片上传以前,会使用系统方法,作一次人脸识别、写入一次Exif信息,这两部都是很是占用内存的。若是并发执行,颇有可能让内容冲到必定高度而Out Of Memory,为了不这个问题,一个是人脸识别只使用一张小图进行识别(不超过640*640),而且对于这两个过程,加锁。(关于iOS里几种锁的用法和优缺点,建议了解一下,面试特别爱问)

3. 将任务放到Queue中

如今咱们已经知道一个任务如何实现了,只须要将NSOperation扔到NSOperation Queue中,就会自动执行了。并发数可使用NSOperation Queue的maxConcurrentOperationCount来控制并发数。

考虑一个问题:何时往Queue里添加NSOperation?(一次性全加入?仍是?)

4. 多线程其余知识

异步任务的同步处理几种方法

  1. NSOperation自定义子类
  2. 使用GCD group的时候,能够对其中异步任务使用dispatch_group_enter和dispatch_group_leave
  3. GCD信号量——dispatch_semaphore ['seməfɔ:]

手动触发KVO

- (void)setHTTPShouldHandleCookies:(BOOL)HTTPShouldHandleCookies {
    [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
    _HTTPShouldHandleCookies = HTTPShouldHandleCookies;
    [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
}

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) {
        return NO;
    }

    return [super automaticallyNotifiesObserversForKey:key];
}

5. 推荐工具

  1. SpaceLauncher
  2. Magnet
  3. MWeb(强烈推荐,几大功能:预览主题、一键把网页变成Markdown、导出、侧边栏)
  4. 小技巧——写markdown的时候,怎么方便的对齐代码

6. 参考文档

  1. 苹果文档NSOperation
  2. https://developer.apple.com/library/content/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationObjects/OperationObjects.html#//apple_ref/doc/uid/TP40008091-CH101-SW1
相关文章
相关标签/搜索