How to Handle Unsafepointer<Unmanaged<Cfarray>>

How to handle UnsafePointerUnmanagedCFArray?

I am a little bit guessing (because I have no experience with the Accessibility
functions), but from the function declaration it should work like this:

let element: AXUIElementRef = ...

var ptr : Unmanaged<CFArray>? = nil
let error = AXUIElementCopyAttributeNames(element, &ptr)
if error == AXError(kAXErrorSuccess) {
let names = ptr!.takeRetainedValue() // gives a CFArray
// ...
}

Update for Swift 3 (untested):

let element: AXUIElement = ...

var cfArray: CFArray?
let error = AXUIElementCopyAttributeNames(element, &cfArray)
if error == .success, let names = cfArray as? [String] {
// names is [String] array ...
}

Cannot convert value of type 'UnsafePointerDouble' to expected argument type 'UnsafePointer_'

So, I was able to solve it, albeit in a roundabout way.

I created a new function convert and used it:

func convertArr<T>(count: Int, data: UnsafePointer<T>) -> [T] {

let buffer = UnsafeBufferPointer(start: data, count: count)
return Array(buffer)
}
...
let doublearrptr = UnsafePointer<Double>(cda)
let arr = convertArr(Int(shobjarrlen), data: doublearrptr)

For some reason this works but not the original syntax...

I'm still open to getting answers from why my original syntax didn't work.

Accessing Objective-C Pointers in Swift

If you pass an UnsafeMutablePointer<Optional<AnyObject>> as the last
argument to AXUIElementCopyAttributeValue() then you must
initialize it by allocating (and ultimately releasing) memory:

var resultPtr: UnsafeMutablePointer<Optional<AnyObject>> = UnsafeMutablePointer.allocate(capacity: 1)
resultPtr.initialize(to: nil)

let result = AXUIElementCopyAttributeValue(appRef, kAXWindowsAttribute as CFString, resultPtr)
// ...

resultPtr.deinitialize()
resultPtr.deallocate(capacity: 1)

It is easier
to pass the address of an Optional<AnyObject> variable
with &. Then conditionally
cast the received object to the expected type, in this case an
array of AXUIElement:

var value: AnyObject?
let result = AXUIElementCopyAttributeValue(appRef, kAXWindowsAttribute as CFString, &value)
if result == .success, let windowList = value as? [AXUIElement] {
// use `windowList`
}

and similarly:

if let window = windowList.first {
var value: AnyObject?
let result = AXUIElementCopyAttributeValue(window, kAXRoleAttribute as CFString, &value)
if result == .success, let role = value as? String {
// use `role` ...
}
}

One could define a generic utility function which encapsulates
all the casting:

func axUICopyAttributeValue<T>(of element: AXUIElement, attribute: String, as type: T.Type) -> T? {
var value: AnyObject?
let result = AXUIElementCopyAttributeValue(element, attribute as CFString, &value)
if result == .success, let typedValue = value as? T {
return typedValue
}
return nil
}

Example usage:

if let windowList = axUICopyAttributeValue(of: appRef, attribute: kAXWindowsAttribute, as:[AXUIElement].self) {

for window in windowList {
if let role = axUICopyAttributeValue(of: window, attribute: kAXRoleAttribute, as: String.self) {

// ...
}
}
}

Error with fetching value from CFArray

You cannot force cast an UnsafePointer<Void> to a type. You must first convert that void pointer to UnsafePointer<Type> then take its memory:

let aPerson = UnsafePointer<ABRecordRef>(CFArrayGetValueAtIndex(allPeople, i)).memory

FYI... ABAddressBook has been deprecated on iOS 9. For new code targeting that OS, use CNContactStore instead.

Working with ObjC Functions with UnsafeMutableRawPointer or UnsafeMutablePointer Parameter from Swift?

AXUIElementCopyAttributeNames does not take a pointer to
an unmanaged CFArray anymore:

let element: AXUIElement = ...

var cfArray: CFArray?
let error = AXUIElementCopyAttributeNames(element, &cfArray)
if error == .success, let names = cfArray as? [String] {
// names is [String] array ...
}

How to convert CFArray to Swift Array?

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.

How to use UnsafeMutablePointerOpaquePointer in Swift?

PMPrinter and PMPaper are defined in the PrintCore framework
as pointer to an "incomplete type"

typedef struct OpaquePMPrinter*         PMPrinter;
typedef struct OpaquePMPaper* PMPaper;

Those are imported into Swift as OpaquePointer, and are a bit
cumbersome to use.

The second argument to PMSessionGetCurrentPrinter() is a pointer to
a non-optional PMPrinter variable, and in Swift it must be
initialized before being passed as an inout argument. One possible way
to initialize a null-pointer is to use unsafeBitCast.

The easiest way to get the PMPaper objects from the array seems to
be to use CFArrayGetValueAtIndex() instead of bridging it to a
Swift array. That returns a UnsafeRawPointer which can be converted
to an OpaquePointer.

This worked in my test:

let printInfo = NSPrintInfo.shared()
let printSession = PMPrintSession(printInfo.pmPrintSession())

var currentPrinter = unsafeBitCast(0, to: PMPrinter.self)
PMSessionGetCurrentPrinter(printSession, ¤tPrinter);

var paperListUnmanaged: Unmanaged<CFArray>?
PMPrinterGetPaperList(currentPrinter, &paperListUnmanaged)
guard let paperList = paperListUnmanaged?.takeUnretainedValue() else {
fatalError()
}
for idx in 0..<CFArrayGetCount(paperList) {
let paper = PMPaper(CFArrayGetValueAtIndex(paperList, idx))!
var width = 0.0, height = 0.0
PMPaperGetWidth(paper, &width)
PMPaperGetHeight(paper, &height)
print(width, height)
}

Swift: CFArray : get values as UTF Strings

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


Related Topics



Leave a reply



Submit