You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI结合Combine实现CPU使用率实时更新失败求助

解决SwiftUI+Combine CPU使用率实时更新问题

问题分析

你的代码首次启动能正确显示CPU数据,但后续无法实时更新,核心问题出在CPU使用率的计算逻辑,而非Combine的Timer配置:

  • 当前代码直接用系统启动以来的累计ticks计算比例,得到的是开机到现在的平均使用率,数值几乎不会变化,看起来像是UI没更新
  • 另外,host_processor_info返回的内存未正确释放,存在内存泄漏风险

修正后的完整代码

import SwiftUI
import Combine

struct ContentView: View {
    
    @StateObject private var cpuMonitor = CPUUsageMonitor()
    
    var body: some View {
        ScrollView {
            VStack {
                Text("CPU核心使用率")
                    .font(.largeTitle)
                    .padding()

                ForEach(cpuMonitor.cpuUsages.enumerated().map { $0 }, id: \.offset) { index, usage in
                    VStack(alignment: .leading) {
                        Text("核心 \(index):")
                            .font(.headline)

                        HStack {
                            Text("用户态: ")
                            Text("\(usage.user, specifier: "%.2f")%")
                        }
                        HStack {
                            Text("系统态: ")
                            Text("\(usage.system, specifier: "%.2f")%")
                        }
                        HStack {
                            Text("Nice: ")
                            Text("\(usage.nice, specifier: "%.2f")%")
                        }
                        HStack {
                            Text("空闲: ")
                            Text("\(usage.idle, specifier: "%.2f")%")
                        }
                    }
                    .padding()
                    .background(Color.gray.opacity(0.1))
                    .cornerRadius(8)
                }
            }
            .padding()
        }
        .onAppear {
            cpuMonitor.startMonitoring()
        }
        .onDisappear {
            cpuMonitor.stopMonitoring()
        }
    }
}

class CPUUsageMonitor: ObservableObject {
    @Published var cpuUsages: [CoreUsage] = []

    private var numCPUs: uint = 0
    private var timerCancellable: AnyCancellable?
    // 保存上一次的CPU ticks数据,用于计算差值
    private var prevTicks: [CoreTicks] = []

    struct CoreTicks {
        var user: UInt64
        var system: UInt64
        var nice: UInt64
        var idle: UInt64
    }

    init() {
        let mibKeys: [Int32] = [CTL_HW, HW_NCPU]
        mibKeys.withUnsafeBufferPointer { mib in
            var sizeOfNumCPUs: size_t = MemoryLayout<uint>.size
            let status = sysctl(mutating: mib.baseAddress, 2, &numCPUs, &sizeOfNumCPUs, nil, 0)
            if status != 0 {
                numCPUs = 1
            }
        }
        cpuUsages = Array(repeating: CoreUsage(user: 0.0, system: 0.0, nice: 0.0, idle: 0.0), count: Int(numCPUs))
        prevTicks = Array(repeating: CoreTicks(user: 0, system: 0, nice: 0, idle: 0), count: Int(numCPUs))
    }

    func startMonitoring() {
        timerCancellable = Timer.publish(every: 1, on: .main, in: .common)
            .autoconnect()
            .sink { [weak self] _ in
                self?.updateCPUUsage()
            }
    }

    func stopMonitoring() {
        timerCancellable?.cancel()
    }

    private func updateCPUUsage() {
        var numCPUsU: natural_t = 0
        var cpuLoadInfo: processor_info_array_t!
        var cpuLoadInfoCount: mach_msg_type_number_t = 0
        
        let err = host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &numCPUsU, &cpuLoadInfo, &cpuLoadInfoCount)
        
        guard err == KERN_SUCCESS else {
            print("获取CPU信息失败!")
            return
        }
        
        defer {
            // 确保当前获取的cpuLoadInfo内存被释放
            let size = MemoryLayout<integer_t>.stride * Int(cpuLoadInfoCount)
            vm_deallocate(mach_task_self_, vm_address_t(bitPattern: cpuLoadInfo), vm_size_t(size))
        }
        
        var updatedUsages: [CoreUsage] = []
        
        for coreIndex in 0..<Int(numCPUs) {
            let offset = CPU_STATE_MAX * Int32(coreIndex)
            let userTicks = UInt64(cpuLoadInfo[Int(offset + CPU_STATE_USER)])
            let systemTicks = UInt64(cpuLoadInfo[Int(offset + CPU_STATE_SYSTEM)])
            let niceTicks = UInt64(cpuLoadInfo[Int(offset + CPU_STATE_NICE)])
            let idleTicks = UInt64(cpuLoadInfo[Int(offset + CPU_STATE_IDLE)])
            
            // 计算两次采样的差值,得到实时使用率
            let deltaUser = userTicks - prevTicks[coreIndex].user
            let deltaSystem = systemTicks - prevTicks[coreIndex].system
            let deltaNice = niceTicks - prevTicks[coreIndex].nice
            let deltaIdle = idleTicks - prevTicks[coreIndex].idle
            
            let totalDelta = deltaUser + deltaSystem + deltaNice + deltaIdle
            
            // 避免除以0的异常情况
            guard totalDelta > 0 else {
                updatedUsages.append(cpuUsages[coreIndex])
                continue
            }
            
            let userUsage = Float(deltaUser) / Float(totalDelta) * 100
            let systemUsage = Float(deltaSystem) / Float(totalDelta) * 100
            let niceUsage = Float(deltaNice) / Float(totalDelta) * 100
            let idleUsage = Float(deltaIdle) / Float(totalDelta) * 100
            
            updatedUsages.append(CoreUsage(user: userUsage, system: systemUsage, nice: niceUsage, idle: idleUsage))
            
            // 更新基准ticks,用于下一次计算
            prevTicks[coreIndex] = CoreTicks(user: userTicks, system: systemTicks, nice: niceTicks, idle: idleTicks)
        }
        
        DispatchQueue.main.async {
            self.cpuUsages = updatedUsages
        }
    }
}

struct CoreUsage {
    var user: Float
    var system: Float
    var nice: Float
    var idle: Float
}

#Preview {
    ContentView()
}

关键修正点

  • 实时使用率计算:保存上一次的CPU ticks数据,通过两次采样的差值比例计算真实的实时使用率,而非累计平均
  • 内存管理优化:用defer语句确保每次host_processor_info返回的内存都被正确释放,避免泄漏
  • UI遍历优化:改用enumerated()遍历数组,更符合SwiftUI的最佳实践
  • 异常防护:增加除以0的判断,避免数值异常

内容的提问来源于stack exchange,提问作者Karl Ehrlich

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 00:06:01