之前在网上看到一个iOS面试题是关于如何创建一个可终止的 Block,链接如下:chuansong.me/n/1467450328129。笔者的思路是通过直接在 GCD 队列中执行一个外部 Block,并在运行前检查相关标识位来判断是否执行。但并没有终止正在运行时 Block 的答案,自己就想挑战下看有没有什么歪门邪路能实现终止正在运行的 Block 😊。
想到要终止正在运行时的 Block,心想必要要有个变量来保存当前的 Block 吧。于是就想到了 NSOperationQueue NSOperation 相关的 ,因为 NSOperation 和 NSOperation 本身就已经提供了相关取消任务的 API 。我们是否可以利用起来呢?
接下来让实践让告诉我们答案吧😁。GCD 中包含三种 NSOperation ,让我们一一来实验下吧。本文 Demo 地址:github.com/Transendence/WCQCancelBlock 大家可以下载 Demo 来一起一步步验证,本人也是新手入门如果发现本文有什么错误可以下方留言跟我反馈,这也是我第一次写分享,无论是文章的排版问题还是其他影响到阅读体验的问题都可以反馈我,大家一起探讨如果我确实写的有问题我会欣然接受并改正,感谢🙏
测试流程:
1.加载相关测试 NSOperation
2.执行 start 启动任务
3.调用 cancel 尝试取消正在执行中任务或 Block
启动/取消函数:
#pragma mark - Event Mthods
- (void)startAction {
[self.blockOperation start];
// [self.invocationOperation start];
// self.operationQueue;
}
- (void)cancelAction {
[self.blockOperation cancel];
// [self.invocationOperation cancel];
// [self.operationQueue cancelAllOperations];
}
测试执行函数:
- (void)run:(NSOperation *)operation {
//if (!operation) operation = self.invocationOperation;
if ([operation isCancelled]) return;
for (NSInteger i = 0; i < 5; i++) {
//if ([operation isCancelled]) return;
sleep(1);
NSLog(@"loop == %@",@(i + 1));
}
}
(1):NSInvocationOperation
- (NSInvocationOperation *)invocationOperation {
if (!_invocationOperation) {
_invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:nil];
}
return _invocationOperation;
}
第一次尝试失败😢,让我们再试试另外个
(2):NSBlockOperation
- (NSBlockOperation *)blockOperation {
if (!_blockOperation) {
__weak __typeof(self) weakSelf = self;
_blockOperation = [NSBlockOperation blockOperationWithBlock:^{
[weakSelf run:_blockOperation];
}];
}
return _blockOperation;
}
失败。自定义的 NSOperation 我们放到后面再将,接下来让我们试试关于 NSOperationQueue 的 cancelAllOperations 方法。
上面两次试验我们发现方法的执行是在当前线程中的,本测试环境的当前线程也就是主线程,线程阻塞了当然使点击取消事件无法响应,必须等 run 函数执行完毕。那我们看看NSOperationQueue呢,默认加入 NSOperationQueue 的 NSOperation 系统都会另开辟一个新的子线程去执行。
(3):NSOperationQueue
- (NSOperationQueue *)operationQueue {
if (!_operationQueue) {
_operationQueue = [[NSOperationQueue alloc] init];
// [_operationQueue addOperation:self.invocationOperation];
[_operationQueue addOperation:self.blockOperation];
// [_operationQueue addOperation:self.cancelBlock];
}
return _operationQueue;
}
发现无论加的是 NSInvocationOperation,NSBlockOperation 还是 NSOperationQueue 都是无法终止正在运行中的任务。通过查看相关资料 [NSOperation cancel] 和 [NSOperationQueue cancelAllOperations] 是无法取消正在执行中的任务。NSOperation 的执行过程大概是 isCanceled?-> Start -> main ,如果想实现终止正在执行的任务,可以通过在自定义的 NSOperation 中重写 main 函数实现该功能。
(4):自定义NSOperation
想终止正在执行的任务,关键是重写的 main 函数中需要定期检查标识位 isCanceled 。如果是则手动 Return 取消当前任务。
- (void)main {
if ([self isCancelled]) return;
for (NSInteger i = 0; i < 5; i++) {
if ([self isCancelled]) return;
sleep(1);
NSLog(@"loop == %@",@(i + 1));
}
}
既然简单的自定义完了 NSOperation,那让我们来加入到 NSOperationQueue 中来测试下是否有效。
成成成成成成成成成成功了!
这个是简单的函数取消,想要实现 Block 的取消, 只要在自定义 NSOperation 中初始化传入相应 Block,原理与上面说明的函数形式相同,大家可以尝试下。
既然大致原理我们都知道了,现在我们再偷懒下,稍微改造下 run 函数,让我们不用自定义 NSOperation 也能实现终止正在运行的任务。代码如下:
- (void)run:(NSOperation *)operation {
NSLog(@"currentThread == %@",[NSThread currentThread]);
if (!operation) operation = self.invocationOperation;
// if ([operation isCancelled]) return;
for (NSInteger i = 0; i < 5; i++) {
// if ([operation isCancelled]) return;
sleep(1);
NSLog(@"loop == %@",@(i + 1));
}
}
请开放 run 函数中加粗代码。然后我们便可以通过 NSInvocationOperation 和 NSBlockOperation 加入到 NSOperationQueue 中的方法来简单的实现终止正在运行任务了。又臭又长的文章终于结束了,感谢大家耐心的看完。欢迎批评指正😄