如何正确调用 CFBinaryHeapGetValues() 并在 Swift 中解析生成的 C 数组?

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

任何人都可以阐明如何在 Swift 中对 Core Foundation 的 CFBinaryHeap 数据结构调用 CFBinaryHeapGetValues 吗?代码 sample/example 会对我有很大帮助。这是我目前的代码:

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

^^如果您查看上面的最后三行,代码在我得到 EXC_BAD_ACCESS 的最后一行失败。我很困惑为什么因为上面的两个打印语句确实有效。我能够验证函数返回的 C 数组中的第一个对象确实是计数为 72 的较小颜色。所以这意味着我传入的数组正在用值更新,但是当我尝试获取句柄时在阵列上通过访问 pointee 事情变得一团糟。起初我认为这是一个内存管理问题,但在阅读 Swift 中桥接的工作原理后,我不确定情况是否如此,尤其是因为这是一个带注释的 CoreFoundation API.

在您的代码中:

CFBinaryHeapAddValue(bh, fooPointer)

您两次通过 UnsafeMutablePointer<CountedColor>

(元素数量影响很多部分。)

所以,调用CFBinaryHeapGetValues(bh, elementPtr)时,需要为C-array预留一个区域,可以包含两个UnsafeMutablePointer<CountedColor>

你应该写成:

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)

还有这段代码:

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

正如我上面写的,elementPtr指向的区域中存储的结果是UnsafeMutablePointer<CountedColor>,而不是UnsafePointer<[CountedColor]>

elementPtr.pointee 检索第一个 UnsafeMutablePointer<CountedColor>,因此您的两个 print 语句有效,但这并不意味着您的代码 returnedValues 包含您期望的内容。

处理 C 数组的一个好方法是创建 UnsafeBufferPointer.

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

UnsafeBufferPointer 用作集合类型,因此您可以轻松地将其转换为 Array.

let values = returnedValues.map{[=14=]!.load(as: CountedColor.self)}

请不要忘记 deinitializedeallocate allocateed 区域。

要做到这一点,您需要保留所有分配的指针及其计数。因此,您可能需要将部分代码更改为:

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)

然后在使用完您的 CFBinaryHeap (bh)...

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

你可能想向 CFBinaryHeap 添加任意数量的 CountedColors,在这种情况下,你可以这样写:

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)
}

(您可能需要将我的代码中具有 2 的所有 count:capacity: 更改为 colors.count。)

然后..

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

无论如何,匹配 initializedeinitializeallocatedeallocate

您可能会在 Swift 标准库中找到一些有用的类型来改进代码,但是一次学习太多可能不是一个好习惯...