前语

信任每一个中高级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;
    MyOperation *operation = _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;
  [_queue removeObject:operation];
  if ([_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];
  };
  if (![_queue containsObject:task]) {
    [_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.调用方法,如下图

iOS |关于RunLoop与空闲队列

3.履行结果

iOS |关于RunLoop与空闲队列

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改写操作,熟练使用它,说不定你比我更会发挥它的优点,尽量减少啥操作都扔子线程就觉得万事大吉的心理吧,并且手艺不嫌多,装装逼也行=。=