🍁
Swift: macのCPU温度を取得する(Intel編)
RunCatのユーザーから度々CPUの温度を表示してよとリクエストをもらっていたので頑張ってみました。
import IOKit
extension FourCharCode {
init(fromString value: StringLiteralType) {
precondition(value.utf8.count == 4)
self = value.utf8.reduce(0) { $0 << 8 + FourCharCode($1) }
}
}
typealias DummyBytes = (UInt8, UInt8, UInt8, UInt8, UInt16,
UInt16, UInt16, UInt32, UInt32, UInt32)
typealias DataBytes = (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)
struct KeyInfo {
var dataSize: IOByteCount = 0
var dataType: UInt32 = 0
var dataAttributes: UInt8 = 0
}
struct InOutStruct {
var key: FourCharCode = 0
var dummy: DummyBytes = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
var keyInfo = KeyInfo()
var padding: UInt16 = 0
var result: UInt8 = 0
var status: UInt8 = 0
var data8: UInt8 = 0
var data32: UInt32 = 0
var bytes: DataBytes = (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)
}
func getCPUTemperature() -> Double {
var conn: io_connect_t = 0
var result: kern_return_t = 0
defer {
// Close Connection
if conn != 0 {
IOServiceClose(conn)
}
}
// Open Connection
guard let matching = IOServiceMatching("AppleSMC") else { return 0 }
let service = IOServiceGetMatchingService(kIOMasterPortDefault, matching)
if service == MACH_PORT_NULL { return 0 }
result = IOServiceOpen(service, mach_task_self_, 0, &conn)
IOObjectRelease(service)
guard result == kIOReturnSuccess else { return 0 }
// Read CPU Temperature
var inputStruct = InOutStruct()
inputStruct.key = FourCharCode(fromString: "TC0P")
inputStruct.keyInfo.dataSize = 2
inputStruct.data8 = 5
var outputStruct = InOutStruct()
let inputSize = MemoryLayout<InOutStruct>.stride
var outputSize = MemoryLayout<InOutStruct>.stride
result = IOConnectCallStructMethod(conn, UInt32(2), &inputStruct, inputSize, &outputStruct, &outputSize)
guard result == kIOReturnSuccess, outputStruct.result == 0 else { return 0 }
// Formatting the Value of Temperature
return Double(256 * Int(outputStruct.bytes.0) + Int(outputStruct.bytes.1)) / 256.0
}
実行例
let value = getCPUTemperature()
print("Temperature: \(value)°C") // Temperature: 50.5625°C
アプリで動かすにはEntitlementsに
<key>com.apple.security.temporary-exception.sbpl</key>
<array>
<string>(allow iokit-open)</string>
</array>
を追加する必要があるみたいです。ただこれだとApp Storeのレビューで却下されるらしいのでダメですね。
所感
FourCharCode
とかIOByteCount
とか見たことない型(typealias)が何個か出てきてワクワクしました。また、最終的な実装には用いていませんが、StaticString
という構造体があることを初めて知りました。IOKit
は奥が深くて、しかもAppleの公式リファレンスがポンコツなので全容が掴めないものですが、先行例を見ながら試行錯誤していくのは発掘の楽しさがありますね。本実装のキモはIOConnectCallStructMethod
ですが、これのinputStruct
とoutputStruct
の構造体の定義を最初に見つけた人のエスパー力には脱帽するしかありません。
てか、たかがCPUの温度を取得するだけで難易度高すぎませんか?
Discussion
マジな人はこういうのを見てエスパー力を高めているみたい こんなのどこで見つけてくるんだか