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.
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 allocate
ed 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 CountedColor
s 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...