MDQueryGetResultAtIndex和UnsafeRawPointer在Swift 3中的含义是什么?

3

我在使用Swift 3和MDQuery进行Spotlight搜索结果探索时遇到了一些问题。我期望MDQueryGetResultAtIndex返回一个MDItem,而在C/Objective-C中,这个假设是成立的,并且我可以调用MDItemCopyAttribute来探索该项。例如,在这里,我成功获取了找到项的路径名:

MDItemRef item = (MDItemRef)MDQueryGetResultAtIndex(q,i);
CFStringRef path = MDItemCopyAttribute(item,kMDItemPath);

但在Swift 3中,MDQueryGetResultAtIndex返回一个UnsafeRawPointer!(在C中是指向void的指针)。为了克服这一点,我尝试过以下方法:

if let item = MDQueryGetResultAtIndex(q, 0) {
    let ptr = item.bindMemory(to: MDItem.self, capacity: 1)
    let path = MDItemCopyAttribute(ptr.pointee, kMDItemPath)
}

但是它崩溃了,日志显示ptr.pointee是一个NSAtom。很明显,我的个人UnsafeRawPointer技巧不起作用(坦白地说,我一直觉得这很困惑)。

我应该如何将这个UnsafeRawPointer转换成我可以成功调用MDItemCopyAttribute的东西呢?

替代方案

  • 我可以将我的Objective-C代码放入Objective-C helper对象中,并从Swift中调用它来解决这个问题;但我想编写一个纯Swift解决方案。

  • 同样地,我可能会重新编写我的代码以使用更高级别的NSMetadataQuery,我也许会这么做;但是,我最初使用较低级别的MDQueryRef的Objective-C代码可以正常工作,所以现在我想知道如何将其直接转换为Swift。


完整代码:如果您想在家试试,请使用以下代码:

let s = "kMDItemDisplayName == \"test\"" // you probably have one somewhere
let q = MDQueryCreate(nil, s as CFString, nil, nil)
MDQueryExecute(q, CFOptionFlags(kMDQuerySynchronous.rawValue))
let ct = MDQueryGetResultCount(q)
if ct > 0 {
    if let item = MDQueryGetResultAtIndex(q, 0) {
        // ... 
    }
}

愚蠢的问题,item?.assumingMemoryBound(to: MDItem.self)?.pointee 获取MDItem怎么样? - JAL
@JAL 的结果与 bindMemory 相同。我也尝试过将它们一起使用,以防万一。 :) - matt
啊,好的,值得一试。我猜你为什么会得到NSAtom是因为你的ptr变成了UnsafePointer(0x1),并且获取它的pointee相当于调用类似(id)0x1的东西,这是一个__NSAtom。我会继续深入挖掘。 - JAL
我会假设你需要通过Unmanaged<MDItem>.fromOpaque(...) 进行操作 - 但我不知道MDQuery的工作原理,所以无法测试。 - Hamish
@JAL 是的,它作为一个指向 Core Foundation 对象的不透明指针(即 void*)引入到 Swift 中。我不会说它是错误地桥接了(除了类型安全漏洞外,将不透明指针返回给你并没有什么真正的问题)。根据我从 MDQueryGetResultAtIndex 文档中的 理解,如果你想要通过提供自定义创建函数来获取自定义对象,那么就有可能得到自定义对象 - 所以我不认为 Swift 可以直接将其桥接回来作为 MDItem(因为它可能不是)。 - Hamish
显示剩余8条评论
1个回答

3
你的代码中存在问题。
if let item = MDQueryGetResultAtIndex(q, 0) {
    let ptr = item.bindMemory(to: MDItem.self, capacity: 1)
    let path = MDItemCopyAttribute(ptr.pointee, kMDItemPath)
}

需要注意的是,UnsafeRawPointer 被解释为指向 MDItem 引用的指针,并在 ptr.pointee 中进行了反引用,但是原始指针本身就是指向 MDItem 的引用,因此反引用次数过多。

将原始指针转换为 MDItem 引用的“最短”方法是使用 unsafeBitCast

let item = unsafeBitCast(rawPtr, to: MDItem.self)

这是直接相当于(Objective-C)强制类型转换的方法。您还可以使用Unmanaged方法将原始指针转换为未管理的引用,然后再转换为托管引用(请参阅如何在Swift中将self强制转换为UnsafeMutablePointer<Void>类型:)。
let item = Unmanaged<MDItem>.fromOpaque(rawPtr).takeUnretainedValue()

这看起来有点复杂,但或许更清楚地表达了意图。后一种方法也适用于保留引用(使用takeRetainedValue())的情况。
自包含示例:
import CoreServices

let queryString = "kMDItemContentType = com.apple.application-bundle"
if let query = MDQueryCreate(kCFAllocatorDefault, queryString as CFString, nil, nil) {
    MDQueryExecute(query, CFOptionFlags(kMDQuerySynchronous.rawValue))

    for i in 0..<MDQueryGetResultCount(query) {
        if let rawPtr = MDQueryGetResultAtIndex(query, i) {
            let item = Unmanaged<MDItem>.fromOpaque(rawPtr).takeUnretainedValue()
            if let path = MDItemCopyAttribute(item, kMDItemPath) as? String {
                print(path)
            }
        }
    }
}

UnsafeRawPointer被解释为指向MDItem引用的指针,然后在ptr.pointee中取消引用,但原始指针就是MDItem引用。这是fromOpaque操作何时需要的关键事实,我从未理解过。正如JAL在上面的评论中所暗示的那样,CFType的桥接方式让我感到困惑。 - matt
马丁的回答很好,马特的问题也很棒。听起来这个API是需要更新为Swift的完美例子。 - JAL
我想我明白@JAL在这里的意思。指向void / UnsafeRawPointer的通常假设是,我们已经转换了指针末端的内容类型。但是当CFTypeRef插入到这里时,正如Martin R所说,它就是指针。这是我直到现在都没有意识到的。 - matt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接