如何将CFArray转换为Swift数组?

28
根据苹果公司的“使用Swift与Cocoa和Objective-C”,“在Swift中,您可以相互转换每对无缝桥接的Foundation和Core Foundation类型。” 这使得使用Core Foundation比实际更简单...
我正在尝试使用从CoreText返回的CFArray。 我有这段代码:
let lines: CFArrayRef  = CTFrameGetLines(frame)

我看到有两种访问这个数组成员的方法。但目前都不能正常工作。


方法一 - 直接使用CFArray

let line: CTLineRef = CFArrayGetValueAtIndex(lines, 0)

这导致出现错误:"'ConstUnsafePointer<()>'在转换为'CTLineRef'时不可转换"。强制转换似乎不能解决此问题。

同样地,我希望像使用Swift数组一样“可互换”地使用行。但是,

let line: CTLineRef = lines[0]

产生错误:“'CFArrayRef'没有名为'subscript'的成员”


方法 #2 - 将CFArray转换为Swift数组

var linesArray: Array = [CTLineRef]()
linesArray = bridgeFromObjectiveC(lines, linesArray.dynamicType)

我在这里声明了一个Swift数组,并将其设置为桥接的CFArray。这段代码可以编译通过,但是当我运行它时,在第二行会导致EXC_BREAKPOINT崩溃。也许我在这个问题上没有正确地使用Swift语言...


看起来使用reinterpretCast可以修复Way #1(将不安全指针转换为CTLineRef)。但是,我很想听听有关#2的反馈。 - user1021430
3
据我最近了解,通常可以通过经过 NSArray 双重转换来将 CFArray 转换为 Swift 中的 Array。(例如:let nsArray = CTFrameGetLines(frame) as NSArray; let array = nsArray as [CTLine])但我还没有尝试过这些 API。 - rickster
感谢您的快速反馈!根据您的写法,代码可以编译通过,但在第二个转换行出现了EXC_BAD_INSTRUCTION崩溃。如果您移除"as [CTLine]"这一行,那么代码可以正常运行,但是在访问数组元素时需要使用reinterpretCast进行重新解释。 - user1021430
据我的理解和使用,rickster的建议是正确的,你可能遇到了编译器错误。还有一点需要注意的是,你应该总是能够将CF类型名称末尾的“Ref”去掉。这样做可以让你使用ARC并且不必显式地释放对象。请参阅此Apple WWDV视频的最后一部分 Swift Interoperability In Depth - Gary Makin
你弄明白了吗? - Andrew
根据开发者论坛的说法,此功能已在6.1中实现。
所有示例行都可以在最新的Xcode 6.1(6A1052c)中编译并按预期工作。与CF对象的互操作性正在改善。如果您遇到其他问题,这将被报告为错误。
- Chris Conover
7个回答

10
从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)

10
这是如何做到的,基于当前Swift编译器和Swift文档的状态。希望在以后的测试版中能够得到改进。
更新:自Beta 5以来,reinterpretCast已更名为unsafeBitCast,并且必须将CTLine对象作为输入发送给它。第二种方法仍然不起作用。

方法一 - 直接使用CFArray

let line: CTLine = reinterpretCast(CFArrayGetValueAtIndex(lines, 0))

关于Gary Makin的评论 - CTLineRef中可以省略Ref,但这不会改变ARC和非ARC。根据《使用Swift与Cocoa和Objective-C》第53-54页,ref和non-ref在编译器中是相同的。尝试调用CFRelease会导致编译器错误。

方法二 - 将CFArray转换为Swift数组 - 目前不可用

理想情况下,我们希望将行转换为CTLine对象的Swift数组,因为我们知道这是由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崩溃。

感谢您发布您的解决方案。您是否已经在此问题上提交了错误报告?我也遇到了同样的问题:http://stackoverflow.com/questions/24519235/swift-extracting-downcasting-cftype-based-coretext-types-in-a-cfarray (自己注意,发布一个更一般性的问题) - Chris Conover
似乎方法2现在可以这样工作: let lines = CTFrameGetLines(frame) as [AnyObject] as! [CTLine] - Denis

7

显然,在Swift 2中,您可以将CFArray转换为[AnyObject]

我花了很多时间弄清楚为什么在从Swift 1.2转换后我的代码停止工作。在我的情况下,原来一些API从CFArray!更改为CFArray?并且这个转换返回了nil:

let cfArray = ... // Function that returns CFArray? instead of CFArray!
if let array = cfArray as? [AnyObject] { // nil
...

我没有收到任何警告,提示我将可选项转换为非可选项,这毫无意义。


4

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

3

刚在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同样有效。


1
是的,这个可以运行。直接将其转换为 Swift 数组会导致错误,但是通过中间转换为 NSArray 就能正常工作。谢谢! - codingFriend1

2
在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
    }
}

我希望有一种更好的方式。

1

Swift 5

extension Array {

    init(_ array: CFArray) {
        self = (0..<CFArrayGetCount(array)).map {
            unsafeBitCast(
               CFArrayGetValueAtIndex(array, $0),
               to: Element.self
            ) 
        }
    }
}

使用方法:

let runs = [CTRun](CTLineGetGlyphRuns(line))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接