ios分析app卡顿问题方案

两个方案,一个是监听runloop,一个是监听FPS

dart 复制代码
import Foundation
import os
/*

 */

class MainRunLoopObserver{
    private var observer:CFRunLoopObserver?
    private var dispatchSourceTimer:DispatchSourceTimer?
    
    //写入日志分析系统
    private let log = OSLog(subsystem: "com.yourapp.performance", category: "RunLoopMonitor")
    
    func startMonitoring(){
#if DEBUG //防止测试代码进入release
        guard observer == nil else{return}
        
        var activity:CFRunLoopActivity = .entry
        switch activity{
        case .entry:
            print("进入runloop")
        case .beforeTimers:
            print("处理Timer之前")
        case .beforeSources:
            print("处理Source之前")
        case .beforeWaiting:
            print("即将休眠")
        case .afterWaiting:
            print("从休眠唤醒")
        case .exit:
            print("退出runloop")
      
        default:
            print("default")
        }
        let observer = CFRunLoopObserverCreateWithHandler(
            kCFAllocatorDefault,
            CFRunLoopActivity.allActivities.rawValue,
            true,
            0
        ) { obs, act in
            activity = act
            //写入
            os_signpost(.begin, log: self.log, name: "RunLoop Activity", "%{public}s", "\(activity)")

        }
        self.observer = observer
        //给主线程Runloop添加观察
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, CFRunLoopMode.commonModes)
        // 创建一个定时器,检测 RunLoop 是否被卡住
        dispatchSourceTimer = DispatchSource.makeTimerSource(queue: DispatchQueue.global(qos: .background))
        // 100ms 轮询
        dispatchSourceTimer?.schedule(deadline: .now(),repeating: 0.1)
        dispatchSourceTimer?.setEventHandler(handler: {[weak self]in
            guard let self = self else{return}
            let startTime = CFAbsoluteTimeGetCurrent()
            DispatchQueue.main.async {
                //计算延迟
                let elapsed = CFAbsoluteTimeGetCurrent() - startTime
                if elapsed > 0.05{ //超过500ms认为就是卡顿
                    print("线程卡顿")
                    os_signpost(.event, log: self.log, name: "UI 卡顿", "RunLoop 卡顿 %d ms", Int(elapsed))
                }
                os_signpost(.end, log: self.log, name: "RunLoop Activity")
                /*
                 如何在 Instruments 里查看 os_signpost 日志
                     1.    打开 Instruments
                     •    在 Xcode 中,点击 Product -> Profile (⌘ + I) 进入 Instruments。
                     2.    选择 "Points of Interest"
                     •    选择 Points of Interest 模板,可以看到 os_signpost 记录的 FPS 下降点。
                     3.    运行应用
                     •    运行 App,触发 FPS 下降(比如滚动复杂 UI),查看 Instruments 的数据。
                 */
            }
        })
        dispatchSourceTimer?.resume()
#endif
    }
    
    func stopMonitoring(){
        if let observer = observer{
            CFRunLoopRemoveObserver(CFRunLoopGetMain(), observer, CFRunLoopMode.commonModes)
            self.observer = nil
        }
        dispatchSourceTimer?.cancel()
        dispatchSourceTimer = nil
    }
}





import QuartzCore

/*
 CADisplayLink 是一个定时器,每次屏幕刷新时调用其回调方法。默认情况下,iOS 设备的屏幕刷新率是 60Hz(每秒 60 帧,iphone16 pro max 会有高刷有120Hz版本),因此如果 CADisplayLink 在 1 秒内执行的次数小于 60 次,就意味着帧率下降,可能出现了卡顿。
 */
class FPSMonitor{
    private var displayLink:CADisplayLink?
    private var lastTimeStamp:TimeInterval = 0
    private var frameCount:Int = 0
    
    //写入到日志系统
    private let log = OSLog(subsystem: "com.yourapp.performance", category: "FPSMonitor")
    
    func startMonitor(){
#if DEBUG
        guard displayLink == nil else{return}
//        每次屏幕刷新时调用 updateFPS
        displayLink = CADisplayLink(target: self, selector: #selector(updateFps))
        displayLink?.add(to: RunLoop.main, forMode: RunLoop.Mode.common)
#endif
    }
    
    //停止监听
    func stopMonitor(){
#if DEBUG
        displayLink?.invalidate()
        displayLink = nil
#endif
    }
    
    @objc
    private func updateFps(link:CADisplayLink){
        if lastTimeStamp == 0{
            lastTimeStamp = link.timestamp
            return
        }
        
        let delta = link.timestamp - lastTimeStamp
        frameCount += 1
        //统计 1 秒内 CADisplayLink 被调用的次数
        if delta >= 1{
            let fps = Double(frameCount) / delta
            lastTimeStamp = link.timestamp
            frameCount = 0
            // 低于 55 FPS 可能意味着轻微卡顿,低于 30 FPS 则可能有严重卡顿
            if fps < 65{
                print("监测到FPS下降:\(Int(fps))")
                //写入到日志系统可以
                os_signpost(.event, log: log, name: "FPS Drop", "%d FPS detected", Int(fps))
                /*
                 如何在 Instruments 里查看 os_signpost 日志
                     1.    打开 Instruments
                     •    在 Xcode 中,点击 Product -> Profile (⌘ + I) 进入 Instruments。
                     2.    选择 "Points of Interest"
                     •    选择 Points of Interest 模板,可以看到 os_signpost 记录的 FPS 下降点。
                     3.    运行应用
                     •    运行 App,触发 FPS 下降(比如滚动复杂 UI),查看 Instruments 的数据。
                 */
            }
        }
    }
}
相关推荐
庞轩px2 小时前
模拟面试回答第十三问:JVM内存模型
jvm·面试·职场和发展
studyForMokey7 小时前
【Android面试】View绘制流程专题
android·面试·职场和发展
酉鬼女又兒8 小时前
零基础快速入门前端CSS Transform 与动画核心知识点及蓝桥杯 Web 应用开发考点解析(可用于备赛蓝桥杯Web应用开发)
开发语言·前端·css·职场和发展·蓝桥杯·html
庞轩px10 小时前
模拟面试回答第十四问:双亲委派模型
jvm·面试·职场和发展·tomcat·类加载·类加载器·双亲委派模型
Mr_Xuhhh10 小时前
LeetCode 热题 100 刷题笔记:数组与排列的经典解法(续)
算法·leetcode·职场和发展
打瞌睡的朱尤11 小时前
3.25蓝桥杯训练
职场和发展·蓝桥杯
j_xxx404_11 小时前
蓝桥杯基础--排序模板合集II(快速,归并,桶排序)
数据结构·c++·算法·蓝桥杯·排序算法
_日拱一卒12 小时前
LeetCode:和为K的子数组
算法·leetcode·职场和发展
Trouvaille ~13 小时前
【优选算法篇】BFS 解决最短路——寻找最优路径的真谛
c++·算法·leetcode·面试·蓝桥杯·宽度优先·最短路问题
harder32114 小时前
Swift 面向协议编程的 RMP 模式
开发语言·ios·mvc·swift·策略模式