iOS |关于RunLoop与空闲队列

前言

相信每一个中高级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.调用方式,如下图

image.png

3.执行结果

image.png

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

猜你喜欢

转载自juejin.im/post/7246746585643827261
今日推荐