Как правильно вызвать CFBinaryHeapGetValues() и проанализировать полученный массив C в Swift?

Может ли кто-нибудь пролить свет на то, как вызывать CFBinaryHeapGetValues ​​в структуре данных Core Foundation CFBinaryHeap в Swift? Пример/пример кода мне бы очень помог. Это код, который у меня есть сейчас:

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. Таким образом, это означает, что массив, который я передаю, обновляется значениями, однако, когда я пытаюсь получить дескриптор на массиве при доступе к указателю все идет наперекосяк. Сначала я подумал, что это связано с управлением памятью, но после прочтения того, как работает мост в Swift, я не уверен, что это так, тем более что это аннотированный API CoreFoundation.


person AyBayBay    schedule 05.12.2016    source источник


Ответы (1)


В вашем коде:

CFBinaryHeapAddValue(bh, fooPointer)

Вы проходите UnsafeMutablePointer<CountedColor> два раза.

(Количество элементов влияет на многие части.)

Итак, при вызове CFBinaryHeapGetValues(bh, elementPtr) вам нужно зарезервировать область для C-массива, которая может содержать два 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>s, а не UnsafePointer<[CountedColor]>.

elementPtr.pointee извлекает первое UnsafeMutablePointer<CountedColor>, поэтому ваши два оператора print работают, но это не означает, что returnedValues вашего кода содержит то, что вы ожидаете.

Одним из хороших способов обработки C-массивов является создание файла UnsafeBufferPointer.

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

UnsafeBufferPointer работает как тип коллекции, поэтому вы можете легко преобразовать его в Array.

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

И, пожалуйста, не забудьте deinitialize и deallocate регионы 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)

Вы можете добавить произвольное количество CountedColors в CFBinaryHeap, в этом случае вы можете написать что-то вроде этого:

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

(Возможно, вам придется изменить все count: или capacity:, имеющие 2 в моем коде, на colors.count.)

А потом..

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

В любом случае, сопоставьте initialize и deinitialize, allocate и deallocate.

Вы можете найти некоторые полезные типы в стандартной библиотеке Swift для улучшения кода, но слишком много вещей за один раз может быть плохой привычкой для изучения...

person OOPer    schedule 05.12.2016
comment
Я проголосовал и принял ваш ответ, большое спасибо! Теперь я понимаю это намного лучше. Один быстрый вопрос. В последней строке кода, которую вы написали, как работает функция load(as:)? Насколько я понимаю, это просто указатель void*. Как же тогда Swift может знать, как читать эти необработанные байты и создавать объект CountedColor. Я читал документацию, и это все еще не помогло. Мне это кажется волшебством, честно говоря, из мира objC, C++ и C, многие взаимодействия Swift с C кажутся мне волшебством в целом :( - person AyBayBay; 05.12.2016
comment
@AyBayBay, как работает эта функция load(as:)? вы должны привести void * к соответствующему типу при извлечении некоторого контента на C, например *((SomeType *)aVoidPointer). В Swift aVoidPointer.load(as: SomeType.self) является его эквивалентом. Или вы можете думать, что это сокращенная форма aVoidPointer.assumingMemoryBound(to: SomeType.self).pointee. - person OOPer; 05.12.2016
comment
Хорошо, большое спасибо :) И последний дополнительный вопрос, я хотел бы просмотреть объекты, которые мне нужно освободить/освободить. Глядя на код, я бы предположил, что это будет callbackPtr, каждый отдельный объект UnsafeMutablePointer‹CountColor›, который я добавил в BinaryHeap, и, наконец, elementPtr и объекты внутри него. Правильно ли я понимаю? - person AyBayBay; 05.12.2016
comment
Просто сказать, что initialize и deinitialize должны совпадать, allocate и deallocate тоже. А с реальным кодом это может быть слишком долго, чтобы быть комментарием, позже я добавлю пример к своему ответу. - person OOPer; 05.12.2016
comment
Хорошо, большое спасибо @OOPer Вы очень помогли тому, что поставило меня в тупик весь день. Я предполагаю, что мой главный вопрос связан только с fooPointer, я использую его и выделяю 2 объекта, которые я добавляю в структуру данных, поэтому я предполагаю, что мне нужно сохранить их в массиве и в какой-то момент также освободить эти 2 CountedColors. Если это имеет смысл, я думаю, что мое понимание хорошо. - person AyBayBay; 05.12.2016
comment
@AyBayBay, обновлено. Надеюсь, что добавленная часть поможет вам лучше понять. - person OOPer; 05.12.2016