3

I call some functions that return a CFArray of CFStringRef values. I need to get utf strings from them. As I didn't want to make my code too complicated, I did the following:

let initString = "\(TISCreateInputSourceList(nil, false).takeUnretainedValue())"

And then I just split the resulting string by \ns to get an array of Swift strings. However, when the function started returning non-ascii strings, trouble started. I started getting strings like "\U2345\U2344".

Then i tried to take the CFArray and iterate over it getting the values and possibly converting them to Strings, but I can't get the values from it:

        let ar = TISCreateInputSourceList(nil, true).takeUnretainedValue()
        for i in 0...CFArrayGetCount(ar) - 1 {
            print(">> ( CFArrayGetValueAtIndex(ar, i).memory )")
        }

The values are always empty.

How can i get the actual values?

Ibolit
  • 9,218
  • 7
  • 52
  • 96

1 Answers1

9

There are some issues here. First, TISCreateInputSourceList() has "Create" in its name which means that it returns a (+1) retained object and you have to take the value with takeRetainedValue(), not takeUnretainedValue(), otherwise the code will leak memory:

let srcs = TISCreateInputSourceList(nil, true).takeRetainedValue()

You could now use the CFArray... methods to get values from the array, but it is much easier to convert it to a NSArray (which is "toll-free bridged"):

let srcs = TISCreateInputSourceList(nil, true).takeRetainedValue() as NSArray

This is not an array of CFStringRef values but an array of TISInputSource objects. You can convert the NSArray to a Swift array:

let srcs = TISCreateInputSourceList(nil, true).takeRetainedValue()
                    as NSArray as! [TISInputSource]

The forced cast as! is acceptable here because the function is documented to return an array of input sources.

Now you can simply iterate over the elements of the array:

for src in srcs  {
    // do something with `src` (which is a `TISInputSource`)
}

The properties of an input source are retrieved with the TISGetInputSourceProperty() function, for example:

let ptr = TISGetInputSourceProperty(src, kTISPropertyInputSourceID)

This returns a "void pointer" (UnsafeMutablePointer<Void>) which has to be converted to an object pointer of the appropriate type (which is CFStringRef for the kTISPropertyInputSourceID property). Unfortunately, this is a bit complicated (compare How to cast self to UnsafeMutablePointer<Void> type in swift):

let val = Unmanaged<CFString>.fromOpaque(COpaquePointer(ptr)).takeUnretainedValue()

Again we can take advantage of toll-free bridging, now from CFStringRef to NSString and String:

let val = Unmanaged<CFString>.fromOpaque(COpaquePointer(ptr)).takeUnretainedValue()
                    as String

Putting it all together:

let srcs = TISCreateInputSourceList(nil, true).takeRetainedValue()
                as NSArray as! [TISInputSource]
for src in srcs  {
    let ptr = TISGetInputSourceProperty(src, kTISPropertyInputSourceID)
    let val = Unmanaged<CFString>.fromOpaque(COpaquePointer(ptr)).takeUnretainedValue()
                as String
    print(val)
}
Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • @Noobie: No. `let srcs = TISCreate....takeRetainedValue()` "transfers the ownership", and the array will be released when `srcs` goes out of scope (since Swift uses ARC) – Martin R Nov 21 '17 at 13:07