创建一个支持异步操作的operation
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的更多相关文章
- 最简单的dockerfile使用教程 - 创建一个支持SSL的Nginx镜像
什么是dockerfile?简单的说就是一个文本格式的脚本文件,其内包含了一条条的指令(Instruction),每一条指令负责描述镜像的当前层(Layer)如何构建. 下面通过一个具体的例子来学习d ...
- 创建一个支持ES6的Nodejs项目
文章来自于:https://www.codementor.io/iykyvic/writing-your-nodejs-apps-using-es6-6dh0edw2o 第一步:创建项目文件夹并初始化 ...
- 转:maven2创建一个eclipse工程,设置M2_REPO
from: http://tonychanhoho.iteye.com/blog/1584324 M2_REPO是一个用来定义 maven 2仓库在硬盘中的存储位置,windows默认是C:\User ...
- 30分钟用Restful ABAP Programming模型开发一个支持增删改查的Fiori应用
2016年时,Jerry曾经写过一系列关于SAP Fiori Smart Template(现在更名为Fiori Elements了)的博客,介绍了所谓的MDD开发方法论 - Metadata Dri ...
- 使用PHP创建一个REST API(Create a REST API with PHP)
译者前言: 首先这是一篇国外的英文文章,非常系统.详尽的介绍了如何使用PHP创建REST API,国内这方面的资料非常非常的有限,而且基本没有可操作性.这篇文章写的非常好,只要对PHP稍有了解的程序员 ...
- 简单创建一个SpringCloud2021.0.3项目(二)
目录 1. 项目说明 1. 版本 2. 用到组件 3. 功能 2. 上一篇教程 3. 创建公共模块Common 4. 网关Gateway 1. 创建Security 2. Security登陆配置 3 ...
- 在一个空ASP.NET Web项目上创建一个ASP.NET Web API 2.0应用
由于ASP.NET Web API具有与ASP.NET MVC类似的编程方式,再加上目前市面上专门介绍ASP.NET Web API 的书籍少之又少(我们看到的相关内容往往是某本介绍ASP.NET M ...
- 搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 (1)
搭建QQ聊天通信的程序:(1)基于 networkcomms.net 创建一个WPF聊天客户端服务器应用程序 原文地址(英文):http://www.networkcomms.net/creating ...
- 5分钟创建一个SpringBoot + Themeleaf的HelloWord应用
第一步:用IDE创建一个普通maven工程,我用的eclipse. 第二步:修改pom.xml,加入支持SpringBoot和Themeleaf的依赖,文件内容如下: <?xml version ...
随机推荐
- rootvg 镜像
具体操作步骤如下 : a) 查看一下当前可用的硬盘: # lspv hdisk0 0002d74f0e69d97a rootvg ...
- 搭建本地Ubuntu 镜像服务器
一.需求分析 最近公司软件Team 有个需求是这样的:能不能在局域网搭建一个Ubuntu 镜像服务器, 这样作的好处是可以节省Ubuntu某些常用工具的安装时间. 二.部署过程 2.1 测试环境 目前 ...
- AndroidUI 视图动画-缩放动画效果 (ScaleAnimation)
放动画效果,可以使用ScaleAnimation: <Button android:id="@+id/btnScale2" android:layout_width=&quo ...
- Java 类加载与初始化
Java系列笔记(1) - Java 类加载与初始化 目录 类加载器 动态加载 链接 初始化 示例 类加载器 在了解Java的机制之前,需要先了解类在JVM(Java虚拟机)中是如何加载的,这对后面理 ...
- 蓝桥杯D1
整数因式分解的问题.了解了一下筛法计算素数的算法.之前都是直接跑的sqrt(n)... 上方的genPrime()即筛法.大致意思是开一个标识数组,通过循环,下标为合数的位置置为false. #inc ...
- android入门——数据存储
首先是SharedPreferences 用户偏好 package com.example.wkp.aboutdata; import android.content.Intent; import a ...
- 第10课_dg
export ORACLE_BASE=/u01/app/oracle export ORACLE_HOME=$ORACLE_BASE/product/10.2.0/db_1 export ORACLE ...
- Java学习之Java实现CallBack功能
回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个函数.而那个函数在需要的时候,利用传递的地址调用回调函数,这时你可以利用这个机会在 ...
- Linux学习之nfs安装配置
安装疑难问题: 1.检查系统是否开启nfs服务:service nfs status 结果显示nfs: unrecognized service,说明系统没有安装nfs服务,so 安装之: 注意,yu ...
- PICT安装及使用
一:PICT安装 1.下载pict33.msi:http://vdisk.weibo.com/s/d6k2tcgXDa7Eq 2.安装: 二:PICT的使用 1.在F:\PICT 目录下,新建一个tx ...