NSOperationQueue时iOS中常用的任务调度机制。在创建一个复杂任务的时候,我们通常都需要编写NSOperation的子类。在大部分情况下,重写main方法就可以满足要求。main方法执行完毕后,系统就会认为这个operation完成了。

有时候情况并没有这么简单。我们需要在operation中调用异步的API,这个API会通过一个block或者代理通知我们结果。这时只靠覆盖main方法就显得力不从心了。因为异步API尚未执行完毕,main方法并不会等待任务执行完毕,而是立即返回,系统就认为operation已经完成了。

怎么解决这个问题呢?我想到AFNetworking中有同样的案例,于是参考了其中的实现,设计了一个基于异步任务的operation。

我们需要覆盖start方法。这个方法的作用有点类似于main方法,在这里完成具体的任务。那么系统怎么知道我们的任务开始执行,或者完成了呢?系统会通过KVO的形式,监听operation的一些属性。我们可以重新实现这些属性,这样系统就可以监听operation执行的状态。

我们需要重新实现这些属性:

@property (readonly, getter=isReady) BOOL ready;
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;

它们都是只读属性。我们可以简单的重写它们,返回我们想要的值。但是,如何通知KVO系统它们的值发生了变化呢?

NSObject的这一对方法能够帮助我们,可以利用它们手动通知系统某个属性发生了变化。

- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;

下面就是完整的代码。这里只用了一个NSTimer模拟一个异步的任务。在state变化时,我们需要通知KVO系统operation的状态发生了变化。这一步很重要,我刚开始忽略了手动通知KVO,导致任务永远无法完成(即使start中的任务全部执行完毕)。

typedef NS_ENUM(NSInteger, MyOperationState) {
MyOperationStateReady,
MyOperationStateExecuting,
MyOperationStateFinished
}; @interface MyOperation : NSOperation @property (nonatomic, strong) NSTimer *exeTimer;
@property (nonatomic, assign) MyOperationState state; // 用来记录operation的状态
@property (nonatomic, strong) NSLock *lock; // 加锁保证线程安全 @end @implementation MyOperation - (instancetype)init
{
self = [super init];
if (self) {
self.lock = [NSLock new];
[self willChangeValueForKey:@"isReady"];
self.state = MyOperationStateReady;
[self willChangeValueForKey:@"isReady"];
}
return self;
} - (void)start
{
[self.lock lock];
if (!self.finished && self.state == MyOperationStateReady) {
// 触发isExecuting属性的KVO观察者,这样系统就知道这个operation已经开始执行了
[self willChangeValueForKey:@"isExecuting"]; self.state = MyOperationStateExecuting; [self didChangeValueForKey:@"isExecuting"];
        // 这里用一个timer模拟一个耗时的任务
self.exeTimer = [NSTimer timerWithTimeInterval: target:self selector:@selector(finish) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.exeTimer forMode:NSRunLoopCommonModes];
}
[self.lock unlock];
} - (void)cancel
{
[self.lock lock];
if (!self.isFinished && !self.cancelled) {
[super cancel];
[self.exeTimer invalidate];
}
[self.lock unlock];
} - (BOOL)isReady
{
return self.state == MyOperationStateReady;
} - (BOOL)isExecuting
{
return self.state == MyOperationStateExecuting;
} - (BOOL)isFinished
{
return self.state == MyOperationStateFinished;
} - (BOOL)isAsynchronous
{
return YES;
} - (BOOL)isConcurrent
{
return YES;
} - (void)finish
{
[self.lock lock];
// 触发isFinished属性的KVO观察者,这样系统就知道这个operation已经执行完毕
    [self willChangeValueForKey:@"isFinished"]; 
self.state = MyOperationStateFinished;
[self didChangeValueForKey:@"isFinished"];
[self.lock unlock];
}
@end

创建一个支持异步操作的operation的更多相关文章

  1. 最简单的dockerfile使用教程 - 创建一个支持SSL的Nginx镜像

    什么是dockerfile?简单的说就是一个文本格式的脚本文件,其内包含了一条条的指令(Instruction),每一条指令负责描述镜像的当前层(Layer)如何构建. 下面通过一个具体的例子来学习d ...

  2. 创建一个支持ES6的Nodejs项目

    文章来自于:https://www.codementor.io/iykyvic/writing-your-nodejs-apps-using-es6-6dh0edw2o 第一步:创建项目文件夹并初始化 ...

  3. 转:maven2创建一个eclipse工程,设置M2_REPO

    from: http://tonychanhoho.iteye.com/blog/1584324 M2_REPO是一个用来定义 maven 2仓库在硬盘中的存储位置,windows默认是C:\User ...

  4. 30分钟用Restful ABAP Programming模型开发一个支持增删改查的Fiori应用

    2016年时,Jerry曾经写过一系列关于SAP Fiori Smart Template(现在更名为Fiori Elements了)的博客,介绍了所谓的MDD开发方法论 - Metadata Dri ...

  5. 使用PHP创建一个REST API(Create a REST API with PHP)

    译者前言: 首先这是一篇国外的英文文章,非常系统.详尽的介绍了如何使用PHP创建REST API,国内这方面的资料非常非常的有限,而且基本没有可操作性.这篇文章写的非常好,只要对PHP稍有了解的程序员 ...

  6. 简单创建一个SpringCloud2021.0.3项目(二)

    目录 1. 项目说明 1. 版本 2. 用到组件 3. 功能 2. 上一篇教程 3. 创建公共模块Common 4. 网关Gateway 1. 创建Security 2. Security登陆配置 3 ...

  7. 在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用

    由于ASP.NET Web API具有与ASP.NET MVC类似的编程方式,再加上目前市面上专门介绍ASP.NET Web API 的书籍少之又少(我们看到的相关内容往往是某本介绍ASP.NET M ...

  8. 搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 (1)

    搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 原文地址(英文):http://www.networkcomms.net/creating ...

  9. 5分钟创建一个SpringBoot + Themeleaf的HelloWord应用

    第一步:用IDE创建一个普通maven工程,我用的eclipse. 第二步:修改pom.xml,加入支持SpringBoot和Themeleaf的依赖,文件内容如下: <?xml version ...

随机推荐

  1. #include <locale.h> #include <locale>

    C C++ C 1 setlocale setlocale,本函数用来配置地域的信息,设置当前程序使用的本地化信息. #include <stdio.h> #include <std ...

  2. string 转换char类型

    将string转换成char类型 const char *c = string.c_str() char转换string char *c_name = "char" string ...

  3. H264源码分析(四)

    sub_mb_pred( mb_type ) {       for( mbPartIdx = 0; mbPartIdx < 4; mbPartIdx++ )                  ...

  4. STL的移动算法

    要在自己定义类型中使用移动算法.须要在元素中提供移动赋值运算符.移动赋值运算符和std::move()详见<c++高级编程>第9章 class mystring { public: str ...

  5. HTTP状态码(HTTP Status Code)【转】

    HTTP状态码(HTTP Status Code) 一些常见的状态码为: 200 - 服务器成功返回网页 404 - 请求的网页不存在 503 - 服务不可用 所有状态解释:点击查看 1xx(临时响应 ...

  6. 使用SuperWebSocket 构建实时 Web 应用

    Web 应用的信息交互过程通常是客户端通过浏览器发出一个请求,服务器端接收和审核完请求后进行处理并返回结果给客户端,然后客户端浏览器将信息呈现出来,这种机制对于信息变化不是特别频繁的应用尚能相安无事, ...

  7. JS前端知识模块大全

    公司前端:小胖提供,表示感谢 1. 基础 HTML, CSS, JS 文档 W3CSCHOOL: http://www.w3schools.com/ MDN: https://developer.mo ...

  8. java.util.vector中的vector的详细用法

    ArrayList会比Vector快,他是非同步的,如果设计涉及到多线程,还是用Vector比较好一些 import java.util.*; /** * 演示Vector的使用.包括Vector的创 ...

  9. Python核心编程读笔 10:函数和函数式编程

    第11章 函数和函数式编程 一 调用函数  1 关键字参数 def foo(x): foo_suite # presumably does some processing with 'x' 标准调用 ...

  10. C++中将int转变成string和string转变成int

    int to string #include<iostream> #include<string> using namespace std; int main() { stri ...