Search code examples
arraysswiftcore-foundationbinary-heap

How to properly call CFBinaryHeapGetValues() and parse the resulting C Array in Swift?


Can anybody please shed some light on how to invoke CFBinaryHeapGetValues on Core Foundation's CFBinaryHeap data structure in Swift? A code sample/example would help me a great deal. This is the code I have currently:

public class CountedColor : NSObject
{
    public private(set) var count: Int
    public private(set) var color: UIColor

    public init(color: UIColor, colorCount: Int)
    {
        self.count = colorCount
        self.color = color
        super.init()
    }
}

var callbacks = CFBinaryHeapCallBacks()

callbacks.compare = { (a,b,unused) in
    let afoo : CountedColor = (a?.assumingMemoryBound(to: CountedColor.self).pointee)!
    let bfoo : CountedColor = (b?.assumingMemoryBound(to: CountedColor.self).pointee)!

    if ( afoo.count == bfoo.count ) { return CFComparisonResult.compareEqualTo }
    if ( afoo.count > bfoo.count ) { return CFComparisonResult.compareGreaterThan }
    return CFComparisonResult.compareLessThan
}

let callbackPointer = UnsafeMutablePointer<CFBinaryHeapCallBacks>.allocate(capacity: 1)
callbackPointer.initialize(to: callbacks)
var bh = CFBinaryHeapCreate(nil, 0, callbackPointer, nil)

var fooPointer : UnsafeMutablePointer<CountedColor>!
fooPointer = UnsafeMutablePointer<CountedColor>.allocate(capacity: 1)
fooPointer.initialize(to: CountedColor(color: UIColor.blue, colorCount: 72))
CFBinaryHeapAddValue(bh, fooPointer)
fooPointer = UnsafeMutablePointer<CountedColor>.allocate(capacity: 1)
fooPointer.initialize(to: CountedColor(color: UIColor.red, colorCount: 99))
CFBinaryHeapAddValue(bh, fooPointer)

var elements = [CountedColor](repeating: CountedColor(color: UIColor.white, colorCount: 0), count: 2)
var elementPtr = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 1)
elementPtr.initialize(to: elements)
CFBinaryHeapGetValues(bh, elementPtr)

var returnedValues: UnsafePointer<[CountedColor]> = (elementPtr.pointee?.assumingMemoryBound(to: [CountedColor].self))!
print(elementPtr.pointee?.assumingMemoryBound(to: CountedColor.self).pointee.count)
print(elementPtr.pointee?.assumingMemoryBound(to: CountedColor.self).pointee.color)
let values = returnedValues.pointee

^^If you look above at the last three lines the code fails at the last line where I get a EXC_BAD_ACCESS. I am confused why because the above two print statements actually work. I am able to verify that the first object in the C array returned by the function is indeed the smaller color with a count of 72. So this means the array I pass in is being updated with values, however when I try to get a handle on the array by accessing pointee things go haywire. At first I thought it was a memory management thing but after reading how bridging works in Swift I am not sure that is the case especially since this is an annotated CoreFoundation API.


Solution

  • In your code:

    CFBinaryHeapAddValue(bh, fooPointer)
    

    You are passing an UnsafeMutablePointer<CountedColor>, two times.

    (The number of elements affect many parts.)

    So, when calling CFBinaryHeapGetValues(bh, elementPtr), you need to reserve a region for C-array which can contain two UnsafeMutablePointer<CountedColor>s.

    You should write it as:

    var elementPtr = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 2)  //<- You need to allocate a region for two elements
    elementPtr.initialize(to: nil, count: 2) //<- The content is overwritten, initial values can be nil
    CFBinaryHeapGetValues(bh, elementPtr)
    

    And this code:

    var returnedValues: UnsafePointer<[CountedColor]> = (elementPtr.pointee?.assumingMemoryBound(to: [CountedColor].self))!
    

    As I wrote above, the result stored in the region pointed by elementPtr are UnsafeMutablePointer<CountedColor>s, not an UnsafePointer<[CountedColor]>.

    elementPtr.pointee retrieves the first UnsafeMutablePointer<CountedColor>, so your two print statements work, but that does not mean returnedValues of your code is holding something you expect.

    One good way to handle C-arrays is creating an UnsafeBufferPointer.

    let returnedValues = UnsafeBufferPointer(start: elementPtr, count: 2)
    

    UnsafeBufferPointer works as a Collection type, so you can easily convert it to an Array.

    let values = returnedValues.map{$0!.load(as: CountedColor.self)}
    

    And please do not forget to deinitialize and deallocate the allocateed regions.

    To make it, you need to keep all the allocated pointers and their counts. So, you may need to change some part of your code as:

    var fooPointer : UnsafeMutablePointer<CountedColor>!
    fooPointer = UnsafeMutablePointer<CountedColor>.allocate(capacity: 1)
    fooPointer.initialize(to: CountedColor(color: UIColor.blue, colorCount: 72))
    CFBinaryHeapAddValue(bh, fooPointer)
    var barPointer : UnsafeMutablePointer<CountedColor>!
    barPointer = UnsafeMutablePointer<CountedColor>.allocate(capacity: 1)
    barPointer.initialize(to: CountedColor(color: UIColor.red, colorCount: 99))
    CFBinaryHeapAddValue(bh, barPointer)
    

    Then after finished using your CFBinaryHeap (bh)...

    elementPtr.deinitialize(count: 2)
    elementPtr.deallocate(capacity: 2)
    barPointer.deinitialize()
    barPointer.deallocate(capacity: 1)
    fooPointer.deinitialize()
    fooPointer.deallocate(capacity: 1)
    

    You may want to add arbitrary number of CountedColors to the CFBinaryHeap, in that case, you can write something like this:

    let colors = [
        CountedColor(color: UIColor.blue, colorCount: 72),
        CountedColor(color: UIColor.red, colorCount: 99),
        //...
    ]
    var fooPointer : UnsafeMutablePointer<CountedColor>!
    fooPointer = UnsafeMutablePointer<CountedColor>.allocate(capacity: colors.count)
    fooPointer.initialize(from: colors)
    for i in colors.indices {
        CFBinaryHeapAddValue(bh, fooPointer+i)
    }
    

    (You may need to change all count:s or capacity:s having 2 in my code to colors.count.)

    And then..

    elementPtr.deinitialize(count: colors.count)
    elementPtr.deallocate(capacity: colors.count)
    fooPointer.deinitialize(count: colors.count)
    fooPointer.deallocate(capacity: colors.count)
    

    Anyway, match initialize and deinitialize, allocate and deallocate.

    You may find some useful types in Swift Standard Library to improve the code, but too much thing at one time may not be a good habit to learn...