Как преобразовать CFArray в Swift Array?
Согласно Apple "Использование Swift с Cocoa и Objective-C", "В Swift вы можете использовать каждую пару необоснованных мостовых Foundation и Core Foundation типов взаимозаменяемо". Это делает работу с Core Foundation более простой, чем на самом деле...
Я пытаюсь работать с CFArray, который возвращается из CoreText. У меня есть этот код:
let lines: CFArrayRef = CTFrameGetLines(frame)
Я вижу два возможных способа доступа к элементам этого массива. Сейчас я не работаю для меня.
Путь # 1 - непосредственно используйте CFArray
let line: CTLineRef = CFArrayGetValueAtIndex(lines, 0)
Это приводит к ошибке "ConstUnsafePointer <() > 'в неконвертируемой форме в" CTLineRef "". Кастинг, похоже, не изменяет эту ошибку.
Точно так же я хотел бы использовать строки "взаимозаменяемо" как массив Swift, как будто он говорит, что я могу. Тем не менее,
let line: CTLineRef = lines[0]
дает ошибку "CFArrayRef" не имеет члена с именем "индекс"
Путь # 2 - Преобразование CFArray в массив Swift
var linesArray: Array = [CTLineRef]()
linesArray = bridgeFromObjectiveC(lines, linesArray.dynamicType)
Здесь я объявил массив Swift и установил его равным мостовому CFArray. Это компилируется без ошибок, но когда я запускаю его, я получаю крах EXC_BREAKPOINT во второй строке. Возможно, я не использую язык Swift правильно на этом...
Ответы
Ответ 1
Вот как это сделать, основываясь на текущем состоянии компилятора Swift и документации Swift. Надеюсь, это будет очищено в более поздних бета-версиях.
UPDATE: Начиная с Beta 5, reinterpretCast был переименован в unsafeBitCast, и объект CTLine должен быть отправлен на него как вход. Путь № 2 все еще не работает.
Путь # 1 - непосредственно используйте CFArray
let line: CTLine = reinterpretCast(CFArrayGetValueAtIndex(lines, 0))
Что касается комментариев Гэри Макина - Ref можно отбросить из CTLineRef, но это не изменит ARC vs non-ARC. Согласно использованию Swift с Cocoa и Objective-C страницами 53-54, ref и non-ref идентичны компилятору. Попытка вызвать CFRelease вызывает ошибку компилятора.
Путь # 2 - Преобразование CFArray в массив Swift - В настоящее время не работает
В идеале мы хотим преобразовать строки в массив Swift объектов CTLine, так как мы знаем, что то, что возвращается CTFrameGetLines, дает нам безопасность типа после преобразования. Вероятно, из-за ошибки компилятора массив можно преобразовать в массив [AnyObject], но не в [CTLine]. Согласно документации Apple, это должно работать:
let linesNS: NSArray = CTFrameGetLines(frame)
let linesAO: [AnyObject] = linesNS as [AnyObject]
let lines: [CTLine] = linesAO as [CTLine]
Это преобразует CFArray в NSArray, затем NSArray в Swift Array [AnyObject], а затем сбрасывает этот массив на конкретный тип CTLine. Это компилируется, но когда он запускается, на последней строке появляется сбой EXC_BREAKPOINT.
Ответ 2
По-видимому, в Swift 2 вы можете использовать CFArray as [AnyObject]
.
Я потратил много времени на выяснение, почему мой код перестает работать после преобразования из Swift 1.2. В моем случае оказалось, что некоторые API изменились с CFArray! к CFArray? и этот листок возвращал ноль:
let cfArray = ... // Function that returns CFArray? instead of CFArray!
if let array = cfArray as? [AnyObject] { // nil
...
Нет никаких предупреждений о том, что я необязательно опционально ставил необязательные, что не имеет смысла.
Ответ 3
на основе ответа от purrrminator:
extension CFArray: SequenceType {
public func generate() -> AnyGenerator<AnyObject> {
var index = -1
let maxIndex = CFArrayGetCount(self)
return anyGenerator{
guard ++index < maxIndex else {
return nil
}
let unmanagedObject: UnsafePointer<Void> = CFArrayGetValueAtIndex(self, index)
let rec = unsafeBitCast(unmanagedObject, AnyObject.self)
return rec
}
}
}
let cfarray = giveMeArray()
for entry in cfarray {
print(entry)
}
Ответ 4
Просто попробовал в XCode 7.3.1 (Swift 2.2.1)
let cfElements = IOHIDDeviceCopyMatchingElements(self.device, nil, IOOptionBits(kIOHIDOptionsTypeNone)).takeUnretainedValue();
let nsElements:NSArray = cfElements
let elements:Array<IOHIDElement> = nsElements as! Array<IOHIDElement>
for element in elements { /**/ }
Я не уверен, нужен ли посредник в NSArray, но для меня это будет делать сейчас 😉
PS: Работает с .takeRetainedValue
.
Ответ 5
В swift 2.0 я сделал преобразование из CFArray в Array со следующим кодом:
extension Array {
static func fromCFArray(records : CFArray?) -> Array<Element>? {
var result: [Element]?
if let records = records {
for i in 0..<CFArrayGetCount(records) {
let unmanagedObject: UnsafePointer<Void> = CFArrayGetValueAtIndex(records, i)
let rec: Element = unsafeBitCast(unmanagedObject, Element.self)
if (result == nil){
result = [Element]()
}
result!.append(rec)
}
}
return result
}
}
Я надеюсь, что есть лучший способ.
Ответ 6
Как и Swift 3, CFArray
можно напрямую связывать с [CTLine]
:
let lines = CTFrameGetLines(frame) as! [CTLine]
guard let firstLine = lines.first else {
return // no line
}
И так же:
let runs = CTLineGetGlyphRuns(firstLine) as! [CTRun]
guard let firstRun = runs.first else {
return // no run
}
(тестируется с Swift 3.1/Xcode 8.3.3 и Swift 4.0/Xcode 9).