前言
相信每一个中高级iOS开发者即使不怎么深入了解过iOS的runLoop机制,但是都听过runLoop这玩意。平时搜索中最多的用处就是用来监听卡顿啥的,但是你们是否知道我们还可以用来做空闲队列的应用呢?
一.场景
假如我们需要下载一堆文件,但是又不是很紧急,这时如果我们选择一次性全量下载,那么即使下载的操作放在了子线程,也会大量占用CPU资源,导致CPU使用率居高不下。而如果CPU使用率达到一定高度时,就无法在规定时间内完成画面的渲染,从而出现肉眼可见的卡顿。这时候我们可以可以通过监听Runloop的空闲状态来进行分批下载文件,每次循环进入空闲状态我们就去下载小部分文件,从而解决卡顿的问题。
二.实战
1.先构建了一个MyOperationQueue,用来做空闲队列,贴出代码如下:
MyOperationQueue.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface MyOperation : NSObject
@end
@interface MyOperationQueue : NSObject
//单例
+ (instancetype)sharedObject;
//添加任务
- (void)addBeforeWaitingQueue:(dispatch_block_t )taskBlock;
@end
NS_ASSUME_NONNULL_END
MyOperationQueue.m
#include <pthread.h>
#import "MyOperationQueue.h"
//beforWaiting的回调
void MyOperationRunLoopObserverWaitingCallBack (CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
//操作类,只做赋值和回调
@interface MyOperation ()
@property (nonatomic, copy) void(^executeOperation)(MyOperation *operation);
//执行函数
- (void)execute;
@end
@implementation MyOperation
- (void)execute
{
if (self.executeOperation) {
self.executeOperation(self);
}
}
@end
#pragma mark - MyOperationQueue
typedef NS_ENUM(NSInteger, MyOperationQueueState) {
MyOperationQueueState_None,
MyOperationQueueState_Start,
MyOperationQueueState_Executing,
MyOperationQueueState_lastExecuteFinish,
MyOperationQueueState_Finish,
};
@interface MyOperationQueue() {
CFRunLoopObserverRef runLoopObserver;
}
@property (nonatomic, assign) MyOperationQueueState state;
@property (nonatomic, strong) NSMutableArray<MyOperation *> *queue;
@property (nonatomic, assign) int interval; // 每个operation最少间隔时间,单位ms
@property (nonatomic, assign) CFAbsoluteTime lastTime;
@end
@implementation MyOperationQueue
+ (instancetype)sharedObject
{
static dispatch_once_t onceToken;
static MyOperationQueue *shareObject;
dispatch_once(&onceToken, ^{
shareObject = [[MyOperationQueue alloc] init];
});
return shareObject;
}
- (void)dealloc
{
[self removeRunloopCallback];
}
- (instancetype)init
{
self = [super init];
if (self) {
_queue = [NSMutableArray new];
_lastTime = CFAbsoluteTimeGetCurrent() * 1000.0;//乘以1000,变成单位毫秒
_interval = 30;//30ms,1000ms=1s
[self initRunloopCallback];
}
return self;
}
- (void)initRunloopCallback
{
//创建上下文
CFRunLoopObserverContext context = {0, (__bridge void *)self, NULL, NULL, NULL};
//创建监听和回调
unLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, YES, 0, &MyOperationRunLoopObserverWaitingCallBack, &context);
//添加监听,主线程回调
CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
}
//释放
- (void)removeRunloopCallback
{
self.state = MyOperationQueueState_None;
if (runLoopObserver) {
CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopDefaultMode);
CFRelease(runLoopObserver);
}
}
//执行函数
- (void)executeOperation
{
switch (self.state) {
case MyOperationQueueState_None:
case MyOperationQueueState_Executing:
case MyOperationQueueState_AllFinish:
return;
default:
break;
}
CFAbsoluteTime curTime = CFAbsoluteTimeGetCurrent() * 1000;
if (curTime - _lastTime < _interval) {//30ms内不执行下一个任务
return;
}
_lastTime = curTime;
@synchronized(self.queue) {
MyOperation *operation = self.queue.firstObject;//取第一个去执行
if (operation) {
self.state = MyOperationQueueState_Executing;
NSLog(@"executeOperation 执行任务:%p",operation);
[operation execute];
}
}
}
//设置状态为开始
- (void)start
{
switch (_state) {
case MyOperationQueueState_Start:
case MyOperationQueueState_Executing:
return;
default:
break;
}
self.state = MyOperationQueueState_Start;
}
//完成了一个任务
- (void)onOperationFinish:(MyOperation *)operation
{
self.state = MyOperationQueueState_lastExecuteFinish;
@synchronized(self.queue) {
[self.queue removeObject:operation];
if ([self.queue count] == 0) {
self.state = MyOperationQueueState_AllFinish;
}
}
}
#pragma mark - public metho
- (void)addBeforeWaitingQueue:(dispatch_block_t )taskBlock
{
MyOperation *task = [[MyOperation alloc] init];
task.executeOperation = ^(MyOperation *operation) {
taskBlock();
//设置该任务完成
[[MyOperationQueue sharedObject] onOperationFinish:operation];
};
@synchronized(self.queue) {
if (![self.queue containsObject:task]) {
[self.queue addObject:task];
NSLog(@"添加任务:%p",task);
}
}
[self start];//开始
}
@end
//runLoop回调
void MyOperationRunLoopObserverWaitingCallBack (CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
MyOperationQueue *opQueue = (__bridge MyOperationQueue *) info;
if (opQueue) {
[opQueue executeOperation];//执行函数
}
}
2.调用方式,如下图
3.执行结果
4.代码说明
在MyOperationQueue
这个单例里面,我创建了一个存放任务的队列queue,每往里面添加一次任务,就把任务添加进我的队列,然后把当前的状态设置成才开始状态(如果当前是执行任务中的状态,就不会设置成开始),利用CFRunLoopAddObserver
添加主线程的runLoop,如果处于空闲状态,则回调给函数MyOperationRunLoopObserverWaitingCallBack
,这个函数会执行队列的executeOperation
函数,executeOperation
函数会去判断当前queue是什么状态,如果是开始状态(start)或者上个任务的完成状态(lastExecuteFinish),则会去取执行队列的下一个任务,然后把队列的状态设置为执行中(Executing),然后回调出去,执行下一个任务,当任务完成,就会调用onOperationFinish
函数,把队列queue的状态设置成上个任务的完成状态(lastExecuteFinish),然后移除队列里已经完成的上一个任务,这时候等待下一次主线程再次进入beforeWaiting状态,回调给函数MyOperationRunLoopObserverWaitingCallBack
,重复刚刚那个过程。
需要注意的有
1.执行到onOperationFinish
函数时,移除完队列里已经完成的上一个任务后,如果队列的所有任务都完成了,就会置成所有任务都完成的状态(AllFinish),这时除非有新的任务加入,把队列状态设置成开始状态(Start),否则每次进入executeOperation
函数都会return掉
2.为了防止短时间内添加大量任务,我在executeOperation
函数添加了30ms的限制,防止任务执行太过频繁,这个可以根据实际情况修改
三.结尾
空闲队列有时候也能用于一些低优先级的UI刷新操作,熟练使用它,说不定你比我更会发挥它的优点,尽量减少啥操作都扔子线程就觉得万事大吉的心理吧,而且手艺不嫌多,装装逼也行=。=