Search code examples
swiftswift3pass-by-referencecore-audiounsafemutablepointer

Swift 3 - Pass struct by reference via UnsafeMutableRawPointer?


In the Core Audio-Framework user data can be passed into callbacks via an UnsafeMutableRawPointer?. I was wondering how to pass a struct by reference via this UnsafeMutableRawPointer?. Changes made inside the callback should be reflected outside the callback.

I set up a playground to test this:

struct TestStruct {
    var prop1: UInt32
    var prop2: Float64
    var prop3: Bool
}

func printTestStruct(prefix: String, data: TestStruct) {
    print("\(prefix): prop1: \(data.prop1), prop2: \(data.prop2), prop3: \(data.prop3)")
}

func testUnsafeMutablePointer(data: UnsafeMutableRawPointer?) {
    var testStructInFunc = data!.load(as: TestStruct.self)

    printTestStruct(prefix: "In func (pre change)", data: testStructInFunc)

    testStructInFunc.prop1 = 24
    testStructInFunc.prop2 = 1.2
    testStructInFunc.prop3 = false

    printTestStruct(prefix: "In func (post change)", data: testStructInFunc)
}

var testStruct: TestStruct = TestStruct(prop1: 12, prop2: 2.4, prop3: true)

printTestStruct(prefix: "Before call", data: testStruct)

testUnsafeMutablePointer(data: &testStruct)

printTestStruct(prefix: "After call", data: testStruct)

Sadly it seems like any changes to testStruct made inside the testUnsafeMutablePointer function are lost after the function call.

I was thinking, that the UnsafeMutableRawPointer behaves like a pass by reference here? What am I missing out?


Solution

  • Your function copies the data into a local struct, but does not copy the modified data back. So this would be a possible solution in your special case:

    func testUnsafeMutablePointer(data: UnsafeMutableRawPointer?) {
        var testStructInFunc = data!.load(as: TestStruct.self)
    
        testStructInFunc.prop1 = 24
        testStructInFunc.prop2 = 1.2
        testStructInFunc.prop3 = false
    
        data!.storeBytes(of: testStructInFunc, as: TestStruct.self)
    }
    

    But note that this works only if the struct contains only "simple" values likes integers and floating point values. "Complex" types like arrays or strings contain opaque pointers to the actual storage and cannot be simply copied like this.

    Another option is to modify the pointed-to struct like this:

    func testUnsafeMutablePointer(data: UnsafeMutableRawPointer?) {
        let testStructPtr = data!.assumingMemoryBound(to: TestStruct.self)
    
        testStructPtr.pointee.prop1 = 24
        testStructPtr.pointee.prop2 = 1.2
        testStructPtr.pointee.prop3 = false
    }
    

    Both solutions assume that the struct still exists when the callback is called, since passing a pointer around does not ensure the lifetime of the pointed-to struct.

    As an alternative, consider to use an instance of a class instead. Passing retained or unretained pointers to the instance allows to control the lifetime of the object while the callback is "active", compare How to cast self to UnsafeMutablePointer<Void> type in swift.