使用Swift查询iOS钥匙串

17

我在使用Swift转换钥匙串查询结果时遇到困难。

我的请求似乎是有效的:

let queryAttributes = NSDictionary(objects: [kSecClassGenericPassword, "MyService",     "MyAccount",       true],
                                   forKeys: [kSecClass,                kSecAttrService, kSecAttrAccount, kSecReturnData])


dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
    var dataTypeRef : Unmanaged<AnyObject>?
    let status = SecItemCopyMatching(queryAttributes, &dataTypeRef);

    let retrievedData : NSData = dataTypeRef!.takeRetainedValue() as NSData
    *** ^^^^can't compile this line^^^^
})

我的问题是,代码无法编译:

Bitcast requires both operands to be pointer or neither
  %114 = bitcast %objc_object* %113 to %PSs9AnyObject_, !dbg !487
Bitcast requires both operands to be pointer or neither
  %115 = bitcast %PSs9AnyObject_ %114 to i8*, !dbg !487
LLVM ERROR: Broken function found, compilation aborted!

我不知道如何将Unmanaged<AnyObject>转换为NSData。有任何想法吗?

我也在尝试访问iOS钥匙串,看到了你的帖子。但我无法弄清如何创建查询字典。我甚至将你上面的第一行代码复制到我的应用程序中,但它显示相同的错误信息:“找不到接受提供的参数的'init'的重载”。我错过了什么吗? - Rob
截至2015年3月11日,最新版本的XCode仍存在同样的问题。 - a432511
如果你正在寻找一个简单的可插入钥匙链包装器,你可以尝试这个:http://github.com/ashleymills/Keychain.swift - Ashley Mills
3个回答

18

使用 withUnsafeMutablePointer 函数和 UnsafeMutablePointer 结构体来检索数据,例如以下示例:

var result: AnyObject?
var status = withUnsafeMutablePointer(&result) { SecItemCopyMatching(queryAttributes, UnsafeMutablePointer($0)) }

if status == errSecSuccess {
    if let data = result as NSData? {
        if let string = NSString(data: data, encoding: NSUTF8StringEncoding) {
            // ...
        }
    }
}

在发布版本(最快的 [-O])中它运行良好。


1
这是我在发布应用程序时唯一有效的解决方案(其他解决方案在Xcode中运行良好,但在beta版本中分发应用程序时无效)。 - Barthelemy
1
这应该是正确的答案。其他的在发布版本中不起作用。 - brettsam
1
你真是个明星,kishikawa。不幸的是,我不能使用你的包装器,因为它需要8.0版本,但这解决了我的问题。 - Maciej Trybiło
1
在我的情况下,这也是唯一可行的解决方案,在发布版本中。此外,我很想知道为什么其他实现在发布版本中不起作用。 - southp
+1 这对我也起作用了。非常感谢!另一种方法只适用于 iPhone 5S+。在 iPhone 5- 上会崩溃。 - a432511

10

看起来你遇到了编译器的一个bug,请及时报告。你可以尝试使用以下不同的方法来检索值:

    var dataTypeRef :Unmanaged<AnyObject>?
    let status = SecItemCopyMatching(queryAttributes, &dataTypeRef);

    let opaque = dataTypeRef?.toOpaque()

    if let op = opaque? {
        let retrievedData = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()

    }

当使用AnyObject作为Unmanaged<T>的类型参数T时,会出现错误。下面的代码片段使用更具体的类型CFError,可以编译通过:
    let url = NSURL(string:"dummy")
    var errorRef: Unmanaged<CFError>?
    let succeeded = CTFontManagerRegisterFontsForURL(url, .Process, &errorRef)

    if errorRef {
        let error = errorRef!.takeRetainedValue()
    }

由于Keychain API根据查询属性返回不同结果,因此需要使用AnyObject


非常感谢您的回答。那我就会进行报告,然后等待下一个测试版看是否已经修复。如果没有修复,我会使用这个代码。再次感谢! - Damien
7
当你将Swift编译器的优化级别设置为-Onone时,似乎这个功能可以正常工作。但是当你将其设置为-O时,Opaque将会是nil。此内容适用于Xcode 6.1(6A1052d)。 - JorgeDeCorte
1
我在let opaque = dataTypeRef?.toOpaque()这一行代码上遇到了一个坏访问异常(Xcode 6.1和6.2b)。 - Michael Waterfall

0

要使其正常工作,您需要首先访问每个钥匙串常量的保留值。例如:

let kSecClassValue = kSecClass.takeRetainedValue() as NSString
let kSecAttrAccountValue = kSecAttrAccount.takeRetainedValue() as NSString
let kSecValueDataValue = kSecValueData.takeRetainedValue() as NSString
let kSecClassGenericPasswordValue = kSecClassGenericPassword.takeRetainedValue() as NSString
let kSecAttrServiceValue = kSecAttrService.takeRetainedValue() as NSString
let kSecMatchLimitValue = kSecMatchLimit.takeRetainedValue() as NSString
let kSecReturnDataValue = kSecReturnData.takeRetainedValue() as NSString
let kSecMatchLimitOneValue = kSecMatchLimitOne.takeRetainedValue() as NSString 

然后,您需要像这样引用在密钥链字典对象中创建的常量。

var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])

我在这篇博客文章中写了关于它的内容:http://rshelby.com/2014/08/using-swift-to-save-and-query-ios-keychain-in-xcode-beta-4/

希望这能有所帮助!

rshelby


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