【问题标题】:How to convert CFArray to Swift Array?如何将 CFArray 转换为 Swift 数组?
【发布时间】:2014-07-10 19:29:12
【问题描述】:

根据 Apple 的“Using Swift with Cocoa and Objective-C”,“在 Swift 中,您可以互换使用每对免费桥接的 Foundation 和 Core Foundation 类型”。这使得使用 Core Foundation 听起来比实际上更简单......

我正在尝试使用从 CoreText 返回的 CFArray。我有这个代码:

let lines: CFArrayRef  = CTFrameGetLines(frame)

我看到了两种可能的方式来访问这个数组的成员。现在两者都不适合我。


方式 #1 - 直接使用 CFArray

let line: CTLineRef = CFArrayGetValueAtIndex(lines, 0)

这会产生错误“'ConstUnsafePointer' in not convertible to '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 修复了方式 #1(将不安全指针转换为 CTLineRef)。不过,我很想得到一些关于 #2 的反馈。
  • 上次我检查过,您通常可以通过 NSArray 进行双重转换,从 CFArray 到 Swift Array。 (例如let nsArray = CTFrameGetLines(frame) as NSArray; let array = nsArray as [CTLine])但我没有尝试过这些 API。
  • 感谢您的快速反馈!正如你所写的那样,它可以编译,但在第二条转换线上有一个 EXC_BAD_INSTRUCTION 崩溃。如果删除“as [CTLine]”,那么它可以工作,但数组元素在访问时必须重新解释。
  • 根据我的理解和使用,rickster 的建议是正确的,您可能遇到了编译器错误。另一条评论,您应该能够始终将“Ref”从 CF 类型的名称末尾删除。这样做允许您使用 ARC 而不必显式释放对象。请参阅此 Apple WWDV 视频的最后一部分 Swift Interoperability In Depth
  • 你有没有想过这个问题?

标签: nsarray swift core-foundation


【解决方案1】:

根据 Swift 编译器和 Swift 文档的当前状态,这里是如何做到这一点的。希望这在以后的测试版中得到清理。

更新:从 Beta 5 开始,reinterpretCast 已重命名为 unsafeBitCast,并且必须将 CTLine 对象作为输入发送给它。方法 #2 仍然不起作用。


方式 #1 - 直接使用 CFArray

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

关于 Gary Makin 的 cmets - 可以从 CTLineRef 中删除 Ref,但这不会改变 ARC 与非 ARC。根据 Using Swift with Cocoa and Objective-C pages 53-54,ref 和 non-ref 与编译器相同。尝试调用 CFRelease 会导致编译器错误。


方式 #2 - 将 CFArray 转换为 Swift 数组 - 目前不工作

理想情况下,我们希望将线条转换为 CTLine 对象的 Swift 数组,因为我们知道这是 CTFrameGetLines 返回的内容,从而在转换后为我们提供类型安全。可能由于编译器错误,该数组可以转换为 [AnyObject] 数组,但不能转换为 [CTLine]。根据Apple's documentation,这应该可以:

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 崩溃。

【讨论】:

  • 感谢您发布解决方法。您是否为此提交了错误?我也遇到了同样的问题:stackoverflow.com/questions/24519235/…(自我提醒,发布一个更一般的问题)
  • 似乎方法 2 现在的工作方式如下: let lines = CTFrameGetLines(frame) as [AnyObject] as! [CT线]
【解决方案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】:

    从 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 测试)。

    【讨论】:

      【解决方案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)
      }
      

      【讨论】:

        【解决方案5】:

        刚刚在 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

        【讨论】:

        • 是的,这行得通。直接转换为 swift 数组会导致错误,但中间转换为 NSArray 可以正常工作。谢谢!
        【解决方案6】:

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

        希望有更好的办法。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-02-19
          • 2014-11-07
          • 2014-08-16
          • 2018-01-08
          • 1970-01-01
          相关资源
          最近更新 更多