import Foundation import IOKit // MARK: - SMC 温度传感器(通过 AppleSMC + IOKit 读取 CPU / GPU 温度) // ── SMC 结构体定义(必须精确匹配 AppleSMC kext 的 SMCParamStruct)── private struct SMCVersion { var major: UInt8 = 0 var minor: UInt8 = 0 var build: UInt8 = 0 var reserved: UInt8 = 0 var release: UInt16 = 0 } private struct SMCPLimitData { var version: UInt16 = 0 var length: UInt16 = 0 var cpuPLimit: UInt32 = 0 var gpuPLimit: UInt32 = 0 var memPLimit: UInt32 = 0 } private struct SMCKeyInfoData { var dataSize: UInt32 = 0 var dataType: UInt32 = 0 var dataAttributes: UInt8 = 0 } private typealias SMCBytes = ( UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8, UInt8 ) private struct SMCParamStruct { var key: UInt32 = 0 var vers = SMCVersion() var pLimitData = SMCPLimitData() var keyInfo = SMCKeyInfoData() var padding: UInt16 = 0 // 关键!对齐填充 var result: UInt8 = 0 var status: UInt8 = 0 var data8: UInt8 = 0 // 子 selector(5=读key, 9=查询key信息, 8=按索引取key) var data32: UInt32 = 0 var bytes: SMCBytes = ( 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 ) } // SMC 常量 private let kSMCHandleYPCEvent: UInt32 = 2 private let kSMCReadKey: UInt8 = 5 private let kSMCGetKeyInfo: UInt8 = 9 private let kSMCGetKeyFromIndex: UInt8 = 8 private let kSMCSuccess: UInt8 = 0 /// 非 actor 工具类:封装 SMC 通信细节 final class SMCSensor { static let shared = SMCSensor() private var conn: io_connect_t = 0 private var cpuKeys: [String] = [] private var gpuKeys: [String] = [] private var scanned = false private init() {} deinit { if conn != 0 { IOServiceClose(conn) } } // MARK: - 公开接口 func readCPUTemperature() -> Double? { return readMax(keys: cpuKeys) } func readGPUTemperature() -> Double? { return readMax(keys: gpuKeys) } // MARK: - 内部 private func readMax(keys: [String]) -> Double? { guard openSMC() else { return nil } if !scanned { scanKeys() } var maxTemp: Double? = nil for key in keys { if let temp = readKey(key) { maxTemp = max(maxTemp ?? temp, temp) } } return maxTemp } private func openSMC() -> Bool { if conn != 0 { return true } let service = IOServiceGetMatchingService( kIOMainPortDefault, IOServiceMatching("AppleSMC") ) guard service != 0 else { return false } let status = IOServiceOpen(service, mach_task_self_, 0, &conn) IOObjectRelease(service) return status == KERN_SUCCESS && conn != 0 } // MARK: - 扫描可用温度 key private func scanKeys() { scanned = true guard conn != 0 else { return } // 先获取 key 总数 let totalKeys = readKeyCount() guard totalKeys > 0 else { return } var cpu: [String] = [] var gpu: [String] = [] for i in 0.. Int { var input = SMCParamStruct() input.key = fourCharCode("#KEY") input.keyInfo.dataSize = 4 input.keyInfo.dataType = fourCharCode("ui32") input.data8 = kSMCReadKey guard let output = callSMC(&input), output.result == kSMCSuccess else { return 0 } let raw = UInt32(output.bytes.0) << 24 | UInt32(output.bytes.1) << 16 | UInt32(output.bytes.2) << 8 | UInt32(output.bytes.3) return Int(raw) } private func keyAtIndex(_ index: Int) -> String? { var input = SMCParamStruct() input.data8 = kSMCGetKeyFromIndex input.data32 = UInt32(index) guard let output = callSMC(&input), output.result == kSMCSuccess else { return nil } return charCodeToString(output.key) } /// 查询 key 的类型和大小,返回 (dataType, dataSize) private func keyInfo(_ keyName: String) -> (String, UInt32)? { var input = SMCParamStruct() input.key = fourCharCode(keyName) input.data8 = kSMCGetKeyInfo guard let output = callSMC(&input), output.result == kSMCSuccess else { return nil } return (charCodeToString(output.keyInfo.dataType), output.keyInfo.dataSize) } /// 读取 key 的值(自动适配 flt / sp78 类型) private func readKey(_ keyName: String) -> Double? { guard let (dataType, dataSize) = keyInfo(keyName) else { return nil } var input = SMCParamStruct() input.key = fourCharCode(keyName) input.keyInfo.dataSize = dataSize input.keyInfo.dataType = fourCharCode(dataType) input.data8 = kSMCReadKey guard let output = callSMC(&input), output.result == kSMCSuccess else { return nil } switch dataType { case "flt ": // 32-bit IEEE 754 float(小端序) let raw = UInt32(output.bytes.0) | (UInt32(output.bytes.1) << 8) | (UInt32(output.bytes.2) << 16) | (UInt32(output.bytes.3) << 24) return Double(Float(bitPattern: raw)) case "sp78": // 16-bit signed fixed-point let sign = (output.bytes.0 & 0x80) == 0 ? 1.0 : -1.0 return sign * (Double(output.bytes.0 & 0x7F) + Double(output.bytes.1) / 256.0) default: return nil } } /// 发送 SMCParamStruct 到 AppleSMC private func callSMC(_ input: inout SMCParamStruct) -> SMCParamStruct? { var output = SMCParamStruct() let size = MemoryLayout.stride // 80 var outputSize = size let result = IOConnectCallStructMethod( conn, kSMCHandleYPCEvent, &input, size, &output, &outputSize ) guard result == KERN_SUCCESS else { return nil } return output } // MARK: - 工具 private func fourCharCode(_ string: String) -> UInt32 { var result: UInt32 = 0 for char in string.utf8 { result = (result << 8) | UInt32(char) } return result } private func charCodeToString(_ code: UInt32) -> String { let c1 = UInt8((code >> 24) & 0xFF) let c2 = UInt8((code >> 16) & 0xFF) let c3 = UInt8((code >> 8) & 0xFF) let c4 = UInt8(code & 0xFF) return String(UnicodeScalar(c1)) + String(UnicodeScalar(c2)) + String(UnicodeScalar(c3)) + String(UnicodeScalar(c4)) } }