当前位置: 首页 > article >正文

iOS 卡顿线上监控

一、核心原理主线程卡顿 RunLoop 超时监听kCFRunLoopBeforeSources / kCFRunLoopAfterWaiting两个状态超过 300ms 没反应 → 判定卡顿 → 抓堆栈 当前页面。测试结果用.dSYM看更详细的信息二、完整代码复制即用import UIKit import Foundation import MachO /// 主线程卡顿监控自动检测卡顿并输出具体方法名 final class CatonMonitor { static let shared CatonMonitor() private init() {} var currentPage 未知 var threshold: TimeInterval 0.3 private var isMonitoring false private var monitorThread: Thread? // MARK: - 启动 / 停止 func start() { guard !isMonitoring else { return } isMonitoring true monitorThread Thread { [weak self] in self?.monitorLoop() } monitorThread?.name com.caton.monitor monitorThread?.start() print(✅ 卡顿监控已启动阈值: \(Int(threshold * 1000))ms) } func stop() { isMonitoring false monitorThread?.cancel() monitorThread nil } // MARK: - 监控循环 private func monitorLoop() { while isMonitoring { var responded false DispatchQueue.main.async { responded true } Thread.sleep(forTimeInterval: threshold) if !responded { report(methods: symbolicateMainThreadStack()) while !responded isMonitoring { Thread.sleep(forTimeInterval: 0.1) } } Thread.sleep(forTimeInterval: 0.1) } } // MARK: - 输出报告 private func report(methods: [String]) { let list methods.map { → \($0) }.joined(separator: \n) let slide Self.appSlide() let addrs methods.compactMap { line - String? in guard let range line.range(of: #\[0x[0-9a-f]\]#, options: .regularExpression) else { return nil } return String(line[range]).dropFirst().dropLast().description }.joined(separator: ) let dsym Self.findDSYMPath() print( 卡顿报告 耗时≥\(Int(threshold * 1000)) ms 卡顿方法\(methods.isEmpty ? 系统内部调用 : \n\(list)) 还原行号终端执行: atos -o \(dsym) -arch arm64 -l \(slide) \(addrs) ) } /// 查找 dSYM 中包含业务代码的 DWARF 文件 private static func findDSYMPath() - String { let execName Bundle.main.executableURL?.lastPathComponent ?? YourApp let home NSHomeDirectory().components(separatedBy: /Library/).first ?? NSHomeDirectory() let derivedData \(home)/Library/Developer/Xcode/DerivedData let fm FileManager.default if let projects try? fm.contentsOfDirectory(atPath: derivedData) { for project in projects where project.hasPrefix(execName) { for config in [Debug-iphonesimulator, Debug-iphoneos, Release-iphonesimulator, Release-iphoneos] { let dwarfDir \(derivedData)/\(project)/Build/Products/\(config)/\(execName).app.dSYM/Contents/Resources/DWARF // 优先找 test.debug.dylibDebug 模式业务代码在这里 let dylib \(dwarfDir)/\(execName).debug.dylib if fm.fileExists(atPath: dylib) { return dylib } // 其次找主二进制 let main \(dwarfDir)/\(execName) if fm.fileExists(atPath: main) { return main } } } } return Bundle.main.executablePath ?? execName } /// 获取业务代码所在二进制的加载基地址 private static func appSlide() - String { let execName Bundle.main.executableURL?.lastPathComponent ?? // 优先找 test.debug.dylibDebug 模式业务代码在这里 for i in 0.._dyld_image_count() { if let name _dyld_get_image_name(i) { let path String(cString: name) if path.hasSuffix(\(execName).debug.dylib) { return String(format: 0x%lx, Int(bitPattern: _dyld_get_image_header(i))) } } } // 其次找主二进制 for i in 0.._dyld_image_count() { if let name _dyld_get_image_name(i), String(cString: name).hasSuffix(execName) { return String(format: 0x%lx, Int(bitPattern: _dyld_get_image_header(i))) } } return 0x0 } // MARK: - 符号化主线程调用栈 private func symbolicateMainThreadStack() - [String] { let addresses captureMainThreadStack() guard !addresses.isEmpty else { return [] } let appModule Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String) as? String ?? let execName Bundle.main.object(forInfoDictionaryKey: CFBundleExecutable) as? String ?? appModule let ignored [CatonMonitor, PageTrack, trackPage, thunk, entry_point] var results: [String] [] for addr in addresses { var info Dl_info() guard dladdr(addr, info) ! 0, let fname info.dli_fname, let sname info.dli_sname else { continue } let file (String(cString: fname) as NSString).lastPathComponent guard file execName || file.hasPrefix(execName .) || file appModule || file.hasPrefix(appModule .) else { continue } let name demangle(String(cString: sname)) if ignored.contains(where: { name.contains($0) }) { continue } if name main || name.hasSuffix(.$main) || name.hasSuffix(.main) { continue } let offset Int(bitPattern: addr) - Int(bitPattern: info.dli_saddr) let hex String(format: 0x%lx, Int(bitPattern: addr)) let desc \(name) \(offset) [\(hex)] if !results.contains(desc) { results.append(desc) } } return results } // MARK: - Mach API 遍历主线程栈帧 private func captureMainThreadStack() - [UnsafeRawPointer] { var list: thread_act_array_t? var count: mach_msg_type_number_t 0 guard task_threads(mach_task_self_, list, count) KERN_SUCCESS, let threads list, count 0 else { return [] } defer { vm_deallocate(mach_task_self_, vm_address_t(bitPattern: threads), vm_size_t(Int(count) * MemoryLayoutthread_act_t.size)) } let main threads[0] thread_suspend(main) defer { thread_resume(main) } #if arch(arm64) var state arm_thread_state64_t() var sc mach_msg_type_number_t(MemoryLayoutarm_thread_state64_t.size / MemoryLayoutnatural_t.size) let kr withUnsafeMutablePointer(to: state) { $0.withMemoryRebound(to: natural_t.self, capacity: Int(sc)) { thread_get_state(main, thread_state_flavor_t(ARM_THREAD_STATE64), $0, sc) } } guard kr KERN_SUCCESS else { return [] } let pc UnsafeRawPointer(bitPattern: UInt(state.__pc)) let fp UnsafeRawPointer(bitPattern: UInt(state.__fp)) #elseif arch(x86_64) var state x86_thread_state64_t() var sc mach_msg_type_number_t(MemoryLayoutx86_thread_state64_t.size / MemoryLayoutnatural_t.size) let kr withUnsafeMutablePointer(to: state) { $0.withMemoryRebound(to: natural_t.self, capacity: Int(sc)) { thread_get_state(main, thread_state_flavor_t(x86_THREAD_STATE64), $0, sc) } } guard kr KERN_SUCCESS else { return [] } let pc UnsafeRawPointer(bitPattern: UInt(state.__rip)) let fp UnsafeRawPointer(bitPattern: UInt(state.__rbp)) #else return [] #endif var addrs: [UnsafeRawPointer] [] if let pc pc { addrs.append(pc) } var cur fp while let f cur, addrs.count 128, isReadable(f) { let frame f.assumingMemoryBound(to: UnsafeRawPointer?.self) if let ra frame[1] { addrs.append(ra) } let next frame[0] if next nil || next cur { break } cur next } return addrs } private func isReadable(_ addr: UnsafeRawPointer) - Bool { var cnt: mach_msg_type_number_t 0 var data: vm_offset_t 0 let kr vm_read(mach_task_self_, vm_address_t(bitPattern: addr), vm_size_t(MemoryLayoutUnsafeRawPointer.size * 2), data, cnt) if kr KERN_SUCCESS { vm_deallocate(mach_task_self_, data, vm_size_t(cnt)); return true } return false } // MARK: - Demangle private func demangle(_ name: String) - String { name.withCString { cStr in guard let p _swift_demangle(cStr, UInt(strlen(cStr)), nil, nil, 0) else { return name } defer { free(p) } return String(cString: p) } } } _silgen_name(swift_demangle) private func _swift_demangle( _ mangledName: UnsafePointerCChar?, _ mangledNameLength: UInt, _ outputBuffer: UnsafeMutablePointerCChar?, _ outputBufferSize: UnsafeMutablePointerUInt?, _ flags: UInt32 ) - UnsafeMutablePointerCChar?三、使用方法import SwiftUI main struct testApp: App { init() { CatonMonitor.shared.start() } var body: some Scene { WindowGroup { ContentView() } } }四、测试卡顿struct ContentView: View { var body: some View { Button(模拟卡顿 (阻塞主线程)) { clickedButton() } } func clickedButton() { // ✅ 不需要任何手动埋点卡顿时自动抓取调用栈 print(开始模拟卡顿...) Thread.sleep(forTimeInterval: 2.0) print(卡顿结束) } }五、你能拿到什么线上定位神器卡顿耗时卡在哪个页面完整方法堆栈机型 系统六、面试必背为什么这套方案准因为UI 操作、事件响应、渲染全都在主线程 RunLoop 里。只要它超时就是真卡顿。七、项目落地把print换成网络上报配合dSYM 解析就能直接定位用户在哪一行代码卡住了。

相关文章:

iOS 卡顿线上监控

一、核心原理主线程卡顿 RunLoop 超时 监听 kCFRunLoopBeforeSources / kCFRunLoopAfterWaiting 两个状态, 超过 300ms 没反应 → 判定卡顿 → 抓堆栈 当前页面。测试结果:用.dSYM看更详细的信息:二、完整代码(复制即用&#xf…...

使用Python进行简单编程

实验一:(1)交互式:(2)文件式:实验2 (1)交互式(2)文件式实验3(1)交互式(2)文件式实验4(1)文件式交互式…...

90度皮带转弯机(CAD)

90度皮带转弯机作为物流输送系统中的关键设备,其核心作用在于实现物料输送路径的灵活转向。在传统直线输送场景中,若需改变物料流向,通常需通过多段直线设备拼接或增设机械臂等复杂结构,这不仅占用大量空间,还会增加设…...

2026实测:CSDN发文按钮消失?1分钟极速找回!

🔍2026实测:CSDN发文按钮消失?1分钟极速找回! 📝摘要:小白友好!CSDN发文按钮找不到/不显示?覆盖缓存清理、权限排查、编辑器误操作全场景,含可复制代码排错流程图&#x…...

2026 独立开发者 AI 工具栈:我的选择和理由

做独立开发者一年半了,工具栈换了好几轮。从最开始什么都试,到现在基本稳定下来。分享一下我目前在用的 AI 相关工具,每个都说说为什么选它、花多少钱。 完整工具栈类别工具月费用途编程 IDECursor Pro135日常写代码终端 AIClaude Code0&…...

leetcode 1405. Longest Happy String 最长快乐字符串-耗时100

Problem: 1405. Longest Happy String 最长快乐字符串 耗时100%,首先按照greedy的策略,每次最多加两个字符,优先队列拿到数量最多的字符,不停的拼起来,延迟放入pre,先拿到cnt和ch再放入pre 最后考虑到某个…...

2026精选课题-基于springboot智慧生活分享平台的设计与实现

专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/学生代理交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...

2026精选课题-基于springboot在线旅游网站系统的设计与实现

专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/学生代理交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...

跃迁Web3数字金融合伙人:Cber经纪人体系全景解析与CMC核心权益拆解

K线背后,真正值钱的往往不是一次买卖,而是关系的复利 加密行业走到今天,单靠“能交易”“币够多”“活动够猛”已经很难撑起长期护城河。行情火热时,人人都像《华尔街》里举着电话咆哮的交易员,恨不得下一分钟就抓住一…...

2026 毕业论文降重 降 AIGC 工具全测评:9 款神器破解毕业焦虑

又到了一年一度的毕业季,知网、维普的重复率检测与 AIGC 疑似度筛查,成了压在无数本科生心头的两座大山。一边是导师对原创性的严苛要求,一边是 AI 写作留下的痕迹难以消除,不少同学在 “改了又查、查了又改” 的循环中熬到深夜。…...

deepseek公式格式

作为一名长期深耕 AI 领域的开发者,你一定遇到过这种“降智”时刻:DeepSeek-R1 给出的推导过程精妙绝伦,满屏的 LaTeX 公式极其专业,但当你试图将其整理成排版精美的文档或 CSDN 博客时,噩梦开始了。直接复制导致公式乱…...

高级java每日一道面试题-2025年8月30日-业务篇[LangChain4j]-如何实现金融文档的自动分析和摘要(如财报、研报)?

在Java生态中,利用LangChain4j实现金融文档(如财报、研报)的自动分析与摘要,是一个典型的RAG(检索增强生成)应用场景。这类文档具有篇幅长、专业术语密集、数据表格多、对精确性要求高等特点,因…...

Using Vulkan -- Window System Integration (WSI)

由于 Vulkan API 可不显示结果即可使用,WSI通过可选 Vulkan 扩展提供。多数实现会包含 WSI 支持。WSI 设计用于将各平台窗口机制与 Vulkan 核心 API 隔离开。 设置可呈现图像的步骤 表面(Surface) VkSurfaceKHR对象与平台无关,设…...

高级java每日一道面试题-2025年8月29日-业务篇[LangChain4j]-如何构建金融知识问答系统?如何处理时效性问题?

基于 LangChain4j 构建金融知识问答系统及时效性处理策略 一、金融知识问答系统的特点与挑战 金融领域知识问答系统面临的核心挑战在于: 领域专精性:金融术语密集、概念复杂,涉及股票、债券、衍生品、财报分析、监管政策等专业内容。准确性要…...

第16章 Android平台构建:《暗黑王朝》的跨平台部署与调试

第16章 Android平台构建:《暗黑王朝》的跨平台部署与调试 在《暗黑王朝》的跨平台开发战略中,Android平台占据了核心地位。与iOS的封闭生态不同,Android的开放性带来了更广阔的市场覆盖,同时也意味着更复杂的碎片化挑战。截至202…...

二叉树的构造、合并与二叉搜索树

文章目录二叉树的构造、合并与二叉搜索树1. 引入:为什么要学习这些?2. 二叉树的构造2.1 从中序与后序遍历构造二叉树2.2 从前序与中序遍历构造二叉树3. 二叉树的合并4. 二叉搜索树(BST)——从无序到有序4.1 从一个生活场景引入4.2…...

27.3k stars!Fish Speech:开源 TTS 的天花板,10 秒克隆任意声音!

Fish Speech:开源 TTS 的天花板,10 秒克隆任意声音 语音合成这件事,曾经是大厂的专属游乐场。现在,一个开源项目用 2700 万行代码和 1000 万小时音频数据,把这道门彻底踹开了。 一、它解决了什么问题? 长期…...

c++基础+类和对象

引用一旦被赋值,就不能再赋其他值??如下图返回返回值的引用意思是返回返回值本身在主函数中调用func函数,该函数返回a的引用(a的别名),出函数后a会被销毁,相当于返回野指针被引用的数…...

2026 SiteGround 官网人工在线客服聊天指南

由于Siteground 近年来为了降低人工压力,隐藏了直接的聊天入口。 不过即便没有登录账号,你依然可以通过“售前咨询(Sales Chat)”的方式找到人工客服。即使你是Siteground 老用户,你可能也并不知道本文提到的这些技巧。…...

高通 QCS8550 边缘智能实践:基于 Qwen2.5-7B 与 Agent+RAG 构建本地化知识助手

1. 高通QCS8550与边缘智能的黄金组合 第一次拿到高通QCS8550开发板时,我完全没想到这块巴掌大的板子能流畅运行70亿参数的大模型。作为高通面向边缘计算推出的旗舰级处理器,QCS8550采用4nm制程工艺,集成了Kryo CPU、Adreno GPU和Hexagon NPU三…...

StructBERT文本相似度模型在网络安全中的应用:恶意文本与钓鱼内容识别

StructBERT文本相似度模型在网络安全中的应用:恶意文本与钓鱼内容识别 最近和几个做安全的朋友聊天,他们都在抱怨一个事儿:现在的网络攻击越来越“聪明”了。钓鱼邮件写得跟真的一样,恶意脚本的注释伪装得人畜无害,社…...

基于计算机视觉的万物识别模型性能优化策略

基于计算机视觉的万物识别模型性能优化策略 你有没有遇到过这样的情况:好不容易部署了一个万物识别模型,结果在实际用的时候,发现识别速度慢得像蜗牛,或者经常把“猫”认成“狗”?别担心,这几乎是每个做计…...

ChatTTS下载安装全攻略:从原理到避坑指南

最近在折腾语音合成项目,发现ChatTTS这个开源工具挺有意思的,功能强大,效果也不错。但在下载安装过程中,确实遇到了不少“坑”,比如环境冲突、依赖版本不对、模型下载慢等等。今天就把我摸索出来的完整安装流程和一些避…...

5个免费IP查询API对比:哪个最适合你的项目?(附性能测试数据)

5个免费IP查询API深度评测:开发者选型指南与实战数据 在构建需要地理位置服务的应用时,IP查询API往往是开发者的首选方案。无论是电商平台的风控系统、内容分发网络的区域优化,还是简单的用户画像分析,一个稳定、精准且免费的IP查…...

《Kubernetes存储篇:基于nfs-subdir-external-provisioner 4.0.18工具自动创建持久化卷》

总结:整理不易,如果对你有帮助,可否点赞关注一下? 更多详细内容请参考:《K8S集群运维指南》 一、简介 1.1、工具简介 nfs-subdir-external-provisioner是一个用于 Kubernetes 的动态存储 Provisioner,它允许你使用已有的 NFS 服务器为集群中的 PVC(持久卷声明)提供动…...

Java+YOLO在医学影像的应用:CT肺结节检测的预处理与后处理优化

摘要:肺癌是全球癌症死亡的首要原因,早期筛查依赖于低剂量螺旋CT(LDCT)中微小结节的精准识别。然而,医学影像数据具有三维体素大、灰度动态范围极宽、背景干扰复杂等特点,直接套用通用2D YOLO模型效果不佳。…...

Java+YOLO在无人货架的应用:商品识别与库存同步的微服务实践

摘要:无人货架(Smart Shelf)作为“最后一公里”的零售终端,其核心难点在于低成本硬件下的高精度商品识别与实时库存同步。传统方案依赖昂贵的重力传感器或纯云端视觉分析,存在成本高、延迟大、弱网易失效等问题。本文提…...

C++数据结构1——可执行文件生成过程

C源代码生成最终可执行文件的过程&#xff0c;通常分为四个核心步骤&#xff1a;预处理(Preprocessing)、编译(Compilation)、汇编(Assembly) 和 链接(Linking)。我们可以通过一个经典的 HelloWorld 程序来完整演示这个过程&#xff1a;// hello.cpp #include <iostream>…...

Java高并发YOLO服务:100路摄像头实时交通标志识别与Redis缓存优化

摘要&#xff1a;在智慧交通系统中&#xff0c;面对成百上千路高清摄像头的实时视频流&#xff0c;传统的“单路单线程”或“Python脚本调用”架构早已不堪重负&#xff0c;导致延迟高企、资源浪费。本文深入探讨如何基于 Java 21 (Virtual Threads) 构建超高并发视频处理流水线…...

COMSOL模拟离子迁移及PH变化:电场、流场与稀物质传递三个物理场的应用

comsol模拟离子迁移PH变化。 应用到电场&#xff0c;流场&#xff0c;稀物质传递三个物理场。实验台上放着微流控芯片样品的时候&#xff0c;突然意识到酸碱度分布对实验结果影响比想象中更大。这时候COMSOL的多物理场耦合功能简直就是救星——把电场、流体、物质迁移三个模块组…...