4

I'm using an Objective-C class in my Swift project via a bridging header. The method signature looks something like this:

- (CFArrayRef)someMethod:(someType)someParameter;

I started by getting an instance of the class, calling the method, and storing the value:

var myInstance = MyClassWithThatMethod();
var cfArr = myInstance.someMethod(someValue);

Then try to get a value in the array:

var valueInArrayThatIWant = CFArrayGetValueAtIndex(cfArr, 0);

However I get the error Unmanaged<CFArray>' is not identical to 'CFArray'. What does Unmanaged<CFArray> even mean?

I looked through How to convert CFArray to Swift Array? but I don't need to convert the array to a swift array (however that would be nice). I just need to be able to get values from the array.

I have also tried the method of passing the CFArray into a function outlined in this answer:

func doSomeStuffOnArray(myArray: NSArray) {

}

However I get a similar error when using it:

doSomeStuffOnArray(cfArr); // Unmanaged<CFArray>' is not identical to 'NSArray'

I am using CFArray because I need to store an array of CGPathRef, which cannot be stored in NSArray.

So how am I supposed to use CFArray in Swift?

Community
  • 1
  • 1
Andrew
  • 15,357
  • 6
  • 66
  • 101
  • Casting a `CGPathRef` to `id` helps in Objective-C, not sure about Swift though. – duci9y Jul 26 '14 at 16:06
  • Could you use a `UIBezierPath` which can contain a `CGPath`, is conforms to `NSCoding` so you should be able to put it in an `NSArray`. – zaph Jul 26 '14 at 16:07
  • @Zaph I considered that, but that seems to make it more difficult than it needs to be. – Andrew Jul 26 '14 at 16:10
  • @duci9y The CGPath's are created and added to the `CFArray` in the Objective-C file, so it might work, as well as what Zaph said. However I am looking for a way to use `CFArrayRef` in Swift. – Andrew Jul 26 '14 at 16:11
  • Why are you intent on using a `CFArrayRef` when the obvious solution is to use native arrays? Any particular reason? – duci9y Jul 26 '14 at 16:12
  • @duci9y I was trying to avoid the conversion from `CGPath` and another class like `UIBezierPath` (and back). It sounds like that step is necessary. – Andrew Jul 26 '14 at 16:16
  • You just need to put a cast to `id`, that's all. And even if you go the `UIBezierPath` way, it is a much, much shorter way than trying to get `CFArrayRef` to work nicely with Swift. – duci9y Jul 26 '14 at 16:17

2 Answers2

8

As explained in Working with Core Foundation Types, there are two possible solutions when you return a Core Foundation object from your own function that is imported in Swift:

  • Annotate the function with CF_RETURNS_RETAINED or CF_RETURNS_NOT_RETAINED. In your case:

    - (CFArrayRef)someMethod:(someType)someParameter CF_RETURNS_NOT_RETAINED;
    
  • Or convert the unmanaged object to a memory managed object with takeUnretainedValue() or takeRetainedValue() in Swift. In your case:

    var cfArr = myInstance.someMethod(someValue).takeUnretainedValue()
    
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • This is a great find, and it fixed the issue at hand. However, the value returned from my `CFArrayGetValueAtIndex` call is of type `ConstUnsafePointer<()>`. Any idea how I might cast that to `CGPathRef`? I guess I've run into [this problem](http://stackoverflow.com/questions/24684750/how-to-convert-cfarray-to-swift-array) – Andrew Jul 26 '14 at 16:28
  • @SantaClaus: I tried a bit but could not find a solution to extract the CGPathRef from the array in Swift. You are probably better off with using NSArray containing UIBezierPath objects. – Martin R Jul 26 '14 at 19:33
  • Yep. I converted my Objective-C code to return an `NSArray` of `UIBezierPath` objects just a bit ago. Its a shame I can't make `CFArray` work though. – Andrew Jul 26 '14 at 19:35
  • @MartinR Do I need to manually call `release` after a `takeRetainedValue` (I'm calling [`UTTypeCreateAllIdentifiersForTag(_:_:_:)`](https://developer.apple.com/documentation/coreservices/1447261-uttypecreateallidentifiersfortag))? If not, how does Swift manage this memory? – Alexander Jul 23 '18 at 17:56
  • @Alexander: No, you don't. After the takeRetainedValue the object is treated by ARC like any other reference type, and a release is sent to the object when it goes out of scope. – Martin R Jul 23 '18 at 18:10
  • @MartinR Ohhh, I see. Do you know of any resources that give more details on the exact workings of these core foundation types in Swift? – Alexander Jul 23 '18 at 19:28
1

An Unmanaged is a wrapper for an actual CF value. (Sort of like an optional.) It's there because ARC can't tell from looking at the declaration of someMethod: whether that method retains the value it returns.

You unwrap an Unmanaged by telling ARC what memory management policy to use for the value inside. If someMethod calls CFRetain on its return value:

let cfArr = myInstance.someMethod(someValue).takeRetainedValue()

If it doesn't:

let cfArr = myInstance.someMethod(someValue).takeUnretainedValue()

After you do that, cfArr is a CFArray, so you can use the bridging tricks from the other questions you linked to for accessing it like a Swift array.

If you own the code for someMethod you can change it a bit to not need this. There's a couple of options for that:

  • Annotate with CF_RETURNS_RETAINED or CF_RETURNS_NOT_RETAINED to tell the compiler what memory behavior is needed
  • Since it's an ObjC method, bridge to NSArray and return that--it'll automatically become an [AnyObject] array in Swift.
rickster
  • 124,678
  • 26
  • 272
  • 326
  • Would bridging it to `NSArray` have any adverse side effects since the `CFArray` contains `CGPathRef`s which cannot be contained in an `NSArray`? – Andrew Jul 26 '14 at 16:45