nsarrayswiftcore-foundation

How to convert CFArray to Swift Array?


According to Apple's "Using Swift with Cocoa and Objective-C", "In Swift, you can use each pair of toll-free bridged Foundation and Core Foundation types interchangeably". This makes working with Core Foundation sound way simpler than it actually is...

I am trying to work with a CFArray that is returned from CoreText. I have this code:

let lines: CFArrayRef  = CTFrameGetLines(frame)

I see two possible ways to access members of this array. Neither is working for me right now.


Way #1 - Use the CFArray directly

let line: CTLineRef = CFArrayGetValueAtIndex(lines, 0)

This yields the error "'ConstUnsafePointer<()>' in not convertible to 'CTLineRef'". Casting does not seem to change this error.

Similarly, I would love to use lines "interchangeably" as a Swift array like it says that I can. However,

let line: CTLineRef = lines[0]

yields the error "'CFArrayRef' does not have a member named 'subscript'"


Way #2 - Convert the CFArray to a Swift array

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

Here, I declared a Swift array and set it equal to the bridged CFArray. This compiles without error, but when I run it, I get an EXC_BREAKPOINT crash on the second line. Perhaps I'm not using the Swift language correctly on this one...


Solution

  • Here is how to do this, based on the current state of the Swift compiler and Swift documentation. Hopefully this gets cleaned up in later betas.

    UPDATE: Since Beta 5, reinterpretCast has been renamed to unsafeBitCast, and a CTLine object must be sent to it as an input. Way #2 still does not work.


    Way #1 - Use the CFArray directly

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

    Regarding Gary Makin's comments - The Ref can be dropped from CTLineRef, but this does not change ARC vs non-ARC. According to Using Swift with Cocoa and Objective-C pages 53-54, ref and non-ref are identical to the compiler. Attempting to call CFRelease causes a compiler error.


    Way #2 - Convert the CFArray to a Swift array - Does not currently work

    Ideally, we want to convert lines to a Swift array of CTLine objects since we know that's what is returned by CTFrameGetLines, giving us type safety after the conversion. Probably due to a compiler bug, the array can be converted to an [AnyObject] array, but not to [CTLine]. According to Apple's documentation, this should work:

    let linesNS: NSArray  = CTFrameGetLines(frame)
    let linesAO: [AnyObject] = linesNS as [AnyObject]
    let lines: [CTLine] = linesAO as [CTLine]
    

    This converts CFArray to NSArray, then NSArray to Swift Array [AnyObject], then downcasts that array to the specific type CTLine. This compiles, but when it is run, there is an EXC_BREAKPOINT crash on the last line.