I love the syntax for the new CoreAudio API. Unfortunately it seems to leak like a sieve. Is there anyway to avoid the leaks without needing to go back to the old API?
This code leaks.
func coreAudioProcessLeak() async throws {
for process in try AudioHardwareSystem.shared.processes {
print(try process.bundleID as Any)
}
}
And this one doesn't but it's not so pretty.
func coreAudioProcessNoLeak() async throws {
var listAddr = AudioObjectPropertyAddress(
mSelector: kAudioHardwarePropertyProcessObjectList,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMain
)
var listBytes: UInt32 = 0
var status = AudioObjectGetPropertyDataSize(
AudioObjectID(kAudioObjectSystemObject),
&listAddr,
0,
nil,
&listBytes
)
guard status == noErr else { throw NSError(
domain: NSOSStatusErrorDomain, code: Int(status), userInfo: nil) }
guard listBytes > 0 else { return }
let count = Int(listBytes) / MemoryLayout<AudioObjectID>.stride
var procIDs = Array<AudioObjectID>(repeating: 0, count: count)
var tmpBytes = listBytes
status = AudioObjectGetPropertyData(
AudioObjectID(kAudioObjectSystemObject),
&listAddr,
0,
nil,
&tmpBytes,
&procIDs
)
guard status == noErr else { throw NSError(
domain: NSOSStatusErrorDomain, code: Int(status), userInfo: nil) }
for procID in procIDs {
var bidAddr = AudioObjectPropertyAddress(
mSelector: kAudioProcessPropertyBundleID,
mScope: kAudioObjectPropertyScopeGlobal,
mElement: kAudioObjectPropertyElementMain
)
// The property’s payload is a CFStringRef
var bidBytes: UInt32 = UInt32(MemoryLayout<CFString?>.size)
var cfStr: CFString? = nil
let err = withUnsafeMutablePointer(to: &cfStr) { cfStr in
AudioObjectGetPropertyData(
procID,
&bidAddr,
0,
nil,
&bidBytes,
cfStr
)
}
if err == noErr, let cfStr {
print(cfStr as String)
}
}
}
I used Instruments to confirm that leaks were happening and they point to for process in try AudioHardwareSystem.shared.processes