克魔助手 - iOS性能检测平台

简介: 众所周知,如今的用户变得越来越关心app的体验,开发者必须关注应用性能所带来的用户流失问题。目前危害较大的性能问题主要有:闪退、卡顿、发热、耗电快、网络劫持等,但是做过iOS开发的人都知道,在开发过程中我们没有一个很直观的工具可以实时的知道开发者写出来的代码会不会造成性能问题,虽然Xcode里提供了耗电量检测、内存泄漏检测等工具,但是这些工具使用效果并不理想(如Leak无法发现循环引用造成的内存泄漏)。所以这篇文章主要是介绍一款实时监控app各项性能指标的工具,包括CPU占用率、内存使用量、内存泄漏、FPS、卡顿检测,并且会分析造成这些性能问题的原因。

前言

众所周知,如今的用户变得越来越关心app的体验,开发者必须关注应用性能所带来的用户流失问题。目前危害较大的性能问题主要有:闪退、卡顿、发热、耗电快、网络劫持等,但是做过iOS开发的人都知道,在开发过程中我们没有一个很直观的工具可以实时的知道开发者写出来的代码会不会造成性能问题,虽然Xcode里提供了耗电量检测、内存泄漏检测等工具,但是这些工具使用效果并不理想(如Leak无法发现循环引用造成的内存泄漏)。所以这篇文章主要是介绍一款实时监控app各项性能指标的工具,包括CPU占用率、内存使用量、内存泄漏、FPS、卡顿检测,并且会分析造成这些性能问题的原因。

CPU

CPU 是移动设备最重要的组成部分,如果开发者写的代码有问题导致CPU负载过高,会导致app使用过程中发生卡顿,同时也可能导致手机发热发烫,耗电过快,严重影响用户体验。 如果想避免CPU负载过高可以通过检测app的CPU使用率,然后可以发现导致CPU过高的代码,并根据具体情况优化。那该如何检测CPU使用率呢?大学期间学过计算机的应该都上过操作系统这门课,学过的都知道线程CPU是调度和分配的基本单位,而应用作为进程运行时,包含了多个不同的线程,这样如果我们能知道app里所有线程占用 CPU 的情况,也就能知道整个app的 CPU 占用率。幸运的是我们在Mach 层中 thread_basic_info 结构体发现了我们想要的东西,thread_basic_info 结构体定义如下:

CPU内存监控

克魔助手提供了分析内存占用、查看 CPU 实时活动数据以及追踪特定应用程序的功能,让开发者可以更好地了解应用程序的运行情况。 以下是一些示例截图:

同样,克魔助手还提供了内存、GPU 性能监控、网络监控等功能,开发者可以查看实时数据活动和追踪应用程序的特定功能。 如下:

内存监控

以下是内存监控的示例截图:

Memory

物理内存(RAM)与 CPU 一样都是系统中最稀少的资源,也是最有可能产生竞争的资源,应用内存与性能直接相关 - 通常是以牺牲别的应用为代价。 不像 PC 端,iOS 没有交换空间作为备选资源,这就使得内存资源尤为重要。

App占用的内存

获取app内存的API同样可以在Mach层找到,mach_task_basic_info 结构体存储了 Mach task 的内存使用信息,其中 resident_size 就是应用使用的物理内存大小,virtual_size 是虚拟内存大小。

#define MACH_TASK_BASIC_INFO 20 /* always 64-bit basic info */ struct mach_task_basic_info { mach_vm_size_t virtual_size; /* virtual memory size (bytes) */ mach_vm_size_t resident_size; /* resident memory size (bytes) */ mach_vm_size_t resident_size_max; /* maximum resident memory size (bytes) */ time_value_t user_time; /* total user run time for terminated threads */ time_value_t system_time; /* total system run time for terminated threads */ policy_t policy; /* default policy for new threads */ integer_t suspend_count; /* suspend count for task */ };

最后得到获取当前 App Memory 的使用情况:

- (CGFloat)usedMemory { task_basic_info_data_t taskInfo; mach_msg_type_number_t infoCount = TASK_BASIC_INFO_COUNT; kern_return_t kernReturn = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)&taskInfo, &infoCount); if (kernReturn != KERN_SUCCESS) { return NSNotFound; } CGFloat value = (CGFloat)(taskInfo.resident_size / 1024.0 / 1024.0); return value; }

设备已使用的内存

+ (CGFloat)deviceUsedMemory { size_t length = 0; int mib[6] = {0}; int pagesize = 0; mib[0] = CTL_HW; mib[1] = HW_PAGESIZE; length = sizeof(pagesize); if (sysctl(mib, 2, &pagesize, &length, NULL, 0) < 0) { return 0; } mach_msg_type_number_t count = HOST_VM_INFO_COUNT; vm_statistics_data_t vmstat; if (host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vmstat, &count) != KERN_SUCCESS) { return 0; } int wireMem = vmstat.wire_count * pagesize; int activeMem = vmstat.active_count * pagesize; return (CGFloat)(wireMem + activeMem) / 1024.0 / 1024.0; }

设备可用的内存

+ (CGFloat)deviceAvailableMemory { vm_statistics64_data_t vmStats; mach_msg_type_number_t infoCount = HOST_VM_INFO_COUNT; kern_return_t kernReturn = host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vmStats, &infoCount); if (kernReturn != KERN_SUCCESS) { return NSNotFound; } return (CGFloat)(vm_page_size * (vmStats.free_count + vmStats.inactive_count) / 1024.0 / 1024.0); }

FPS

FPS即屏幕每秒的刷新率,范围在0-60之间,60最佳。FPS是测量用于保存、显示动态视频的信息数量,每秒钟帧数愈多,所显示的动作就会愈流畅,优秀的app都要保证FPS 在 55-60 之间,这样才会给用户流畅的感觉,反之,用户则会感觉到卡顿。 对于FPS的计算网上争议颇多,这边使用的和 YYKit 中的 YYFPSLabel 原理一样,系统提供了 CADisplayLink 这个 API,该API在屏幕每次绘制的时候都会回调,通过接收 CADisplayLink 的回调,计算每秒钟收到的回调次数得到屏幕每秒的刷新次数,从而得到 FPS,具体代码如下:

首先,为了使用克魔助手检查iOS游戏的帧率,需要在电脑上安装爱思助手或者iTunes驱动。随后,通过USB连接苹果手机到电脑,并启动克魔助手。

设备选择

第二步,在工具左侧菜单栏中,打开“设备”窗口。窗口中会显示你连接的所有苹果设备,选择要查询的设备,然后选中设备。

查看FPS和fps监测

第三步,点击“开始监听”,在右侧的“FPS曲线”查看当前应用程序的帧率。另外,在克魔助手中可以看到每秒图像

更新多少次,以及总图像更新次数,从而了解游戏性能如何。

CPU占比和应用追踪及特定APP数据

第四步,查看帧率是保证游戏顺畅性的重要因素,所以应用程序开发人员将会经常使用克魔助手检查苹果手机玩游戏的帧率。当然,游戏还有很多需要优化的地方,可以提高游戏整体性能,所以克魔助手不仅提供了监控帧率,还提供了可以分析内存占用,查看CPU实时活动数据,以及追踪特定app时等功能,让开发者可以更好地了解游戏运行情况。

通过克魔助手的帮助,开发者可以方便快捷地检查苹果手机玩游戏的帧率。了解帧率和游戏性能对增加游戏用户量,增强用户体验和游戏的质量都非常重要,所以在开发过程中不要忘记检查苹果手机的帧率。

值得注意的是基于 CADisplayLink 实现的 FPS 在生产场景中只有指导意义,不能代表真实的 FPS,因为基于 CADisplayLink 实现的 FPS 无法完全检测出当前 Core Animation 的性能情况,它只能检测出当前 RunLoop 的帧率。

Freezing

为什么会出现卡顿

从一个像素到最后真正显示在屏幕上,iPhone 究竟在这个过程中做了些什么?想要了解背后的运作流程,首先需要了解屏幕显示的原理。iOS 上完成图形的显示实际上是 CPU、GPU 和显示器协同工作的结果,具体来说,CPU 负责计算显示内容,包括视图的创建、布局计算、图片解码、文本绘制等,CPU 完成计算后会将计算内容提交给 GPU,GPU 进行变换、合成、渲染后将渲染结果提交到帧缓冲区,当下一次垂直同步信号(简称 V-Sync)到来时,最后显示到屏幕上

上文中提到 V-Sync 是什么,以及为什么要在 iPhone 的显示流程引入它呢?在 iPhone 中使用的是双缓冲机制,即上图中的 FrameBuffer 有两个缓冲区,双缓冲区的引入是为了提升显示效率,但是与此同时,他引入了一个新的问题,当视频控制器还未读取完成时,比如屏幕内容刚显示一半时,GPU 将新的一帧内容提交到帧缓冲区并把两个缓冲区进行交换后,视频控制器就会把新的一帧数据的下半段显示到屏幕上,造成画面撕裂现象,V-Sync 就是为了解决画面撕裂问题,开启 V-Sync 后,GPU 会在显示器发出 V-Sync 信号后,去进行新帧的渲染和缓冲区的更新。

搞清楚了 iPhone 的屏幕显示原理后,下面来看看在 iPhone 上为什么会出现卡顿现象,上文已经提及在图像真正在屏幕显示之前,CPU 和 GPU 需要完成自身的任务,而如果他们完成的时间错过了下一次 V-Sync 的到来(通常是1000/60=16.67ms),这样就会出现显示屏还是之前帧的内容,这就是界面卡顿的原因(离屏渲染就是典型的卡顿问题)。不难发现,无论是 CPU 还是 GPU 引起错过 V-Sync 信号,都会造成界面卡顿。

那如何检测卡顿呢?比较常见的思路是:开辟一条单独的子线程,让这条子线程去实时检测主线程的 RunLoop 情况,实时计算 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting 两个状态之间的耗时是否超过某个阀值,如果超过阈值即认定主线程发生了卡顿。下面是代码实现:

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { MyClass *object = (__bridge MyClass*)info; // 记录状态值 object->activity = activity; // 发送信号 dispatch_semaphore_t semaphore = moniotr->semaphore; dispatch_semaphore_signal(semaphore); } - (void)registerObserver { CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context); CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); // 创建信号 semaphore = dispatch_semaphore_create(0); // 在子线程监控时长 dispatch_async(dispatch_get_global_queue(0, 0), ^{ while (YES) { // 假定连续5次超时50ms认为卡顿(当然也包含了单次超时250ms) long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC)); if (st != 0) { if (activity==kCFRunLoopBeforeSources || activity==kCFRunLoopAfterWaiting) { if (++timeoutCount < 5) continue; // 检测到卡顿,进行卡顿上报 } } timeoutCount = 0; } }); }

当检测到卡顿后可以进一步收集卡顿现场,如堆栈信息等,关于收集堆栈信息这里就不细说,很多第三方库都有实现,我之前是使用了项目中已经集成的收集崩溃信息的三方库,通过这个库在收集堆栈信息。

MemoryLeak

内存泄漏也是造成app内存过高的主要原因,如果iPhone手机的性能都很强,如果一个app会因为内存过高被系统强制杀掉,大部分都是存在内存泄漏。内存泄漏对于开发和测试而言表现得并不明显,如果它不泄漏到一定程度是用户是无法察觉的,但是这也是开发者必须杜绝的一大问题。

查找内存泄漏

对于内存泄漏Xcode提供了Leak工具,但是使用过的人都知道Leak无法查出很多泄漏(如循环引用),在这里检测内存泄漏使用的是微信读书团队 Mr.佘 提供的工具 MLeakFinder。 这里大致讲一下实现原理,当一个VC(或者View)被pop或者被dismiss 2 秒后还没有被销毁则认定该VC(或View)发生了泄漏。那如何知道 2 秒后该对象有没有被释放呢,

+ (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [self swizzleSEL:@selector(popViewControllerAnimated:) withSEL:@selector(swizzled_popViewControllerAnimated:)]; }); }

通过方法交换将系统的pop方法换掉,然后注入自己的代码实现,当调用pop时会调用 willDealloc 方法,该方法实现如下:

- (BOOL)willDealloc { __weak id weakSelf = self; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ __strong id strongSelf = weakSelf; [strongSelf assertNotDealloc]; }); return YES; }

添加图片注释,不超过 140 字(可选)

通过弱引用持有自身,并在 2 秒后调用 assertNotDealloc, 如果 2 秒内该对象已释放这里的 weakSelf 为nil,也就什么都不会发生,反之则认为发生了内存泄漏,进行下一步操作,如弹出警告等。 这里只是大致讲一下 MLeakFinder 的原理,详细介绍可以去 https://keymob.com/详细了解。

查找循环引用

查找循环引用使用的是 Facebook 开源库 FBRetainCycleDetector ,具体也可以去网上查找相关资料,这里就不详细说。

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
1月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
109 1
|
2月前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
10天前
|
监控 算法 iOS开发
深入探索iOS函数调用栈:符号化与性能调优实战
在iOS开发中,理解函数调用栈对于性能调优和问题排查至关重要。函数调用栈记录了程序执行过程中的函数调用顺序,通过分析调用栈,我们可以识别性能瓶颈和潜在的代码问题。本文将分享iOS函数调用栈的基本概念、符号化过程以及如何利用调用栈进行性能调优。
29 2
|
2月前
|
监控 Android开发 iOS开发
深入探索安卓与iOS的系统架构差异:理解两大移动平台的技术根基在移动技术日新月异的今天,安卓和iOS作为市场上最为流行的两个操作系统,各自拥有独特的技术特性和庞大的用户基础。本文将深入探讨这两个平台的系统架构差异,揭示它们如何支撑起各自的生态系统,并影响着全球数亿用户的使用体验。
本文通过对比分析安卓和iOS的系统架构,揭示了这两个平台在设计理念、安全性、用户体验和技术生态上的根本区别。不同于常规的技术综述,本文以深入浅出的方式,带领读者理解这些差异是如何影响应用开发、用户选择和市场趋势的。通过梳理历史脉络和未来展望,本文旨在为开发者、用户以及行业分析师提供有价值的见解,帮助大家更好地把握移动技术发展的脉络。
92 6
|
2月前
|
IDE 开发工具 Android开发
安卓与iOS开发对比:平台选择对项目成功的影响
【9月更文挑战第10天】在移动应用开发的世界中,选择正确的平台是至关重要的。本文将深入探讨安卓和iOS这两大主要移动操作系统的开发环境,通过比较它们的市场份额、开发工具、编程语言和用户群体等方面,为开发者提供一个清晰的指南。我们将分析这两个平台的优势和劣势,并讨论如何根据项目需求和目标受众来做出最佳选择。无论你是初学者还是有经验的开发者,这篇文章都将帮助你更好地理解每个平台的特性,并指导你做出明智的决策。
|
2月前
|
安全 Android开发 数据安全/隐私保护
安卓与iOS的对决:移动操作系统的性能与创新
在当今智能手机市场,安卓和iOS两大操作系统一直处于竞争状态。本文将深入探讨它们在性能、安全性和用户体验方面的不同,并分析这些差异如何影响用户的选择。
55 3
|
2月前
|
开发工具 Android开发 iOS开发
安卓与iOS开发:平台选择的艺术与科学
在移动应用开发的广阔天地中,安卓与iOS两大平台如同东西方哲学的碰撞,既有共通之处又各具特色。本文将深入探讨这两个平台的设计理念、开发工具和市场定位,旨在为开发者提供一份简明扼要的指南,帮助他们在这场技术与商业的博弈中找到自己的道路。通过比较分析,我们将揭示每个平台的优势与局限,以及它们如何影响应用的性能、用户体验和市场接受度。无论你是初涉江湖的新手,还是经验丰富的老手,这篇文章都将为你的选择提供新的视角和思考。
37 5
|
2月前
|
人工智能 Android开发 iOS开发
安卓与iOS开发:平台选择的艺术
在移动应用开发的广阔天地里,安卓和iOS两大操作系统各占半壁江山。本文将深入探讨这两个平台的开发环境、工具及市场趋势,帮助开发者在选择适合自己项目的平台时做出更明智的决策。通过比较各自的优势与局限,我们不仅能更好地理解每个系统的核心特性,还能洞察未来技术发展的脉络。无论你是刚入行的新手还是资深开发者,这篇文章都将为你提供有价值的参考和启示。
48 5
|
2月前
|
Linux Android开发 iOS开发
探索Android与iOS开发:平台之战还是互补共生?
在移动应用开发的浩瀚宇宙中,Android和iOS这两大星系始终吸引着无数开发者的目光。它们各自拥有独特的引力场,引领着技术潮流的方向。本文将穿梭于这两个平台的星际空间,揭示它们背后的力量对比,以及如何在这两者之间找到平衡点,共同推动移动应用开发的进步。
39 1
|
2月前
|
移动开发 开发框架 Android开发
安卓与iOS开发:平台之战的新篇章
在移动应用开发的广阔天地中,安卓和iOS始终占据着主导地位。本文通过比较这两个平台的发展历程、技术特点及未来趋势,探讨了它们之间的竞争与合作。文章旨在为开发者提供一个清晰的平台选择指南,并预测未来移动开发的可能走向。
48 1