1

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

1 Answer 1

1

Wow, that's real bad. It doesn't look like the process array itself or the AudioHardwareProcess wrappers are leaking as weak references to them are able to become nil, so it's something else, probably the property data.

I tried making the not-so-pretty version leak by retaining both objectIDs and bundleID CFStrings yet the Apple swift wrapper was still leaking twice as fast as me.

This incredible ability to leak makes the boring version look pretty good, however if you want a nice swift wrapper check out @sbooth's CAAudioHardware project . I haven't used it but it's a very readable overview of CoreAudio objects and properties!

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks. I'll check out the CAAudioHardware project. And it seems like everything thing leaks not just AudioHardwareProcess. Epic fail Apple.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.