iOS 任务调度器:为 CPU 和内存减负
来源:indulge_in     阅读:529
云上智慧
发布于 2019-01-09 18:38
查看主页
GitHub 地址:YBTaskScheduler

支持 cocopods,使用简便,效率不错,一个性能优化的基础组件。

前言

前些时间有好几个技术朋友问过笔者相似的问题:主线程需要执行大量的任务导致卡慢如何解决?异步任务量级过大导致 CPU 和内存压力过高如何优化?

处理相似的问题可以用几个思路:降频、淘汰、优先级调度。

原本处理这些问题并不需要很复杂的代码,但是涉及到少量 C 代码并且要注意线程安全的问题,所以笔者就做了这样一个轮子,以处理任务调度引发的性能问题。

本文讲述 YBTaskScheduler 的原理,读者朋友需要有肯定的 iOS 基础,理解少量性能优化的知识,基本用法可以先看看 GitHub README,DEMO 中也有一个相册列表的应用案例。

一、需求分析

就拿 DEMO 中的案例来说明,一个显示相册图片的列表:


实现图中业务,必然考虑到几个耗时操作:

天经地义的想四处理方案(DEMO中有实现):

一整套流程下来,貌似需求很好的处理了,但是当快速滑动列表时,会发现 CPU 和内存的占用会比较高(这取决于从相册中读取并显示多大的图片)。当然 DEMO 中按照屏幕的物理像素解决,就算不使用任务调度器组件快速滑动列表也基本不会有掉帧的现象。考虑到老旧设施或者者技术人员的水平,很多时候这种需求会导致严重的 CPU 和内存负担,甚至导致闪退。

以上解决方案可能存在的性能瓶颈:

任何一种情况都可能导致用户端卡死或者者闪退,结合业务来分析问题,会发现优化的思路还是不难找到:

没错, YBTaskScheduler 组件就是替你做了这些事情 ??,而且还不止于此。

二、命令模式与 RunLoop

想要管理这些复杂的任务,并且在合适的时机调用它们,自然而然的就想到了命令模式。意味着任务不能直接执行,而是把任务作为一个命令装入容器。

在 Objective-C 中,显然 Block 代码块能处理推迟执行这个问题:

[_scheduler addTask:^{     /*      具体任务代码     解压图片、裁剪图片、访问磁盘等      */}];

而后组件将这些代码块“装起来”,组件由此“掌握”了所有的任务,可以自由的决定何时调用这些代码块,何时对某些代码块进行淘汰,还可以实现优先级调度。

既然是命令模式,还差一个 Invoker (调用程序),即何时去触发这些任务。结合 iOS 的技术特点,可以监听 RunLoop 循环周期来实现:

static void addRunLoopObserver() {    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        taskSchedulers = [NSHashTable weakObjectsHashTable];        CFRunLoopObserverRef observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopBeforeWaiting | kCFRunLoopExit, true, 0xFFFFFF, runLoopObserverCallBack, NULL);        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);        CFRelease(observer);    });}

而后在回调函数中进行任务的调度。

三、策略模式

考虑到任务的淘汰策略和优先级调度,必然需要少量高效数据结构来支撑,为了提高解决效率,笔者直接使用了 C++ 的数据结构:dequepriority_queue

由于要实现任务淘汰,所以使用deque双端队列来模拟栈和队列,而不是直接使用stackqueue。使用priority_queue优先队列来解决自己设置的优先级调度,它的缺点是不能删除低优先级节点,为了节约时间成本姑且够用。

具体的策略:

实际上组件是推荐使用栈和队列这两种策略,由于插入和取出的时间复杂度是常数级的,需要定制任务的优先级时才考虑使用优先队列,由于其插入复杂度是 O(nlogn) 的。

至此,整个组件的业务是比较清晰了,组件需要让这三种解决方式可以自由的变动,所以采用策略模式来解决,下面是 UML 类图:

UML类图

嗯,这是个挺标准的策略模式??。

四、线程安全

因为任务的调度可能在任意线程,所以必需要做好容器(栈、队列、优先队列)访问的线程安全问题,组件是使用pthread_mutex_tdispatch_once来保证线程安全,同时笔者尽量减少临界区来提高性能。值得注意的是,假如不会存在线程安全的代码就不要去加锁了。

关于多线程及安全可以看笔者的另一篇文章:iOS 如何高效的使用多线程,这里就不赘述了。

后语

部分技术细节就不多说了,组件代码量比较少,假如感兴趣可以直接看源码。实际上这个组件的应用场景并不是很多,在项目稳固需要做深度的性能优化时可能会比较需要它,并且希望使用它的人也能理解少量原理,做到胸有成竹,才能灵活的运用。

免责声明:本文为用户发表,不代表网站立场,仅供参考,不构成引导等用途。 系统环境 服务器应用
相关推荐
使用JavaScript+Selenium玩转Web应用自动化测试之等待(Waits)
Nodejs探秘:深入了解单线程实现高并发原理
阿里P8架构师谈:网站动静分离架构设计、以及优劣势、适用场景
android 记一次处理键盘遮挡问题
作为Java小白,equals和hashCode的这点关系,你不得不知道啊!
首页
搜索
订单
购物车
我的