钥匙串类不适用于 iPhone 4s/5 和 iPad

Posted

技术标签:

【中文标题】钥匙串类不适用于 iPhone 4s/5 和 iPad【英文标题】:Keychain class doesn't work for iPhone 4s/5 and iPad 【发布时间】:2014-10-18 10:29:42 【问题描述】:

我将userIdkeychain 一起保存在我的ios 应用程序的设备上。这适用于 iPhone 5s/6/6 Plus,但不适用于 iPhone 4s/5 和 iPad。在说明我的应用程序时,我检查userId 是否存在于钥匙串中,func tokenExists() 是否存在。但后来我得到一个错误:

fatal error: unexpectedly found nil while unwrapping an Optional value 

如下所示:

这是我的钥匙串类,用于保存、加载和删除令牌并检查令牌是否存在:

import UIKit
import Security

// Identifiers
let userAccount:String = "userAccount"

class KeychainService: NSObject 

/*
save token to keychain
*/
func deleteToken() 
    self.delete("userId")


/*
save token to keychain
*/
func saveToken(token: NSString) 
    self.save("userId", data: token)


/*
load keychain token
*/
func loadToken() -> NSString? 
    var token: AnyObject? = self.load("userId")

    return token as? NSString


func tokenExists() -> Bool 

    let token:NSString = loadToken()!

    if (token == "") 
        return false
    
    else 
        return true
    


private func delete(service: NSString) 
    //var dataFromString: NSData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
    // Instantiate a new default keychain query
    var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount])

    // Delete any existing items
    SecItemDelete(keychainQuery as CFDictionaryRef)

    // Check that it worked ok
    //println("Token deleted")


private func save(service: NSString, data: NSString) 
    var dataFromString: NSData = data.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
    // Instantiate a new default keychain query
    var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, dataFromString], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecValueData])

    // Delete any existing items
    SecItemDelete(keychainQuery as CFDictionaryRef)

    // Add the new keychain item
    var status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil)

    // Check that it worked ok
    //println("Saving status code is: \(status)")


private func load(service: NSString) -> AnyObject? 
    // Instantiate a new default keychain query
    // Tell the query to return a result
    // Limit our results to one item
    var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPassword, service, userAccount, kCFBooleanTrue, kSecMatchLimitOne], forKeys: [kSecClass, kSecAttrService, kSecAttrAccount, kSecReturnData, kSecMatchLimit])

    // I'm not too sure what's happening here...
    var dataTypeRef :Unmanaged<AnyObject>?

    // Search for the keychain items
    let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)

    //println("Loading status code is: \(status)")

    // I'm not too sure what's happening here...
    let opaque = dataTypeRef?.toOpaque()
    var contentsOfKeychain: NSString?

    if let op = opaque? 
        let retrievedData = Unmanaged<NSData>.fromOpaque(op).takeUnretainedValue()
        //println("Retrieved the following data from the keychain: \(retrievedData)")
        var str = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
        contentsOfKeychain = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
        //println("The decoded string is \(str)")
     else 
        //println("Nothing was retrieved from the keychain.")
    

    return contentsOfKeychain


我的问题有什么解决方案吗?

【问题讨论】:

【参考方案1】:

从 XCode 6.1 中包含的更新开始,我现在必须将状态与 errSecItemNotFound 进行比较:

var dataTypeRef: Unmanaged<AnyObject>?
let status: OSStatus = SecItemCopyMatching(keychainQuery as CFDictionaryRef, &dataTypeRef)
if (status != errSecItemNotFound) 
    if let dataType = dataTypeRef? 
        ...
    

在此更新之前,我还在使用if let op = opaque? ,它正确地捕捉到它是nil,但后来我开始因为尝试解开零值而出现错误。

希望这可以帮助其他在同一问题上偶然发现此答案的人。

【讨论】:

【参考方案2】:

您的问题是您的 tokenExists() 方法没有检查令牌是否存在。相反,它假设 tokenExists(如 let token:NSString = loadToken()! 末尾的 ! 所述),然后仅检查它是否为空字符串。

试试这个tokenExists() 方法:

func tokenExists() -> Bool 
    var result = false
    if let token = loadToken() 
        // the token exists, but is it blank?
        if token != "" 
            // no, the token is not blank
            result = true
        
    
    return result

【讨论】:

如果这是解决方案,请检查绿色标记。很高兴它对你有用。

以上是关于钥匙串类不适用于 iPhone 4s/5 和 iPad的主要内容,如果未能解决你的问题,请参考以下文章

UIPageViewController - 以编程方式更改页面在 iPhone 4s/5/5s 上不起作用

AssetCatalog LaunchImage 仅横向 - iPhone 4S/5/5S 未显示

推送通知在 iPhone 3GS 中不起作用。它适用于最新的 IOS 设备,如 iPhone 4、4s、5

如何处理 iPhone 4s、5 和 6 的屏幕尺寸,并使所有内容在所有设备上看起来都一样

无法在越狱的 iphone 4s 5.1.1 上打开 xcode 4.5.1 应用程序

indexPath.row 在 iPhone 6 模拟器上的计算不正确,但不是 4s/5/5s/6plus