Getting Optional("") when trying to get value from KeyChain
Asked Answered
R

5

15

When I try to get my keyChain value, it return a string containing:

Optional("[thing in the KeyChain]")

so, I tried to remove "Optional" by using a loop:

var str = KeychainService.loadToken()

for(var i = 0; i < 9 ; i++)
{
    str[i] = ""
}

But i get a error: NSString does not have a member named 'subscript'

The KeychainService class:

import Foundation
import Security

let serviceIdentifier = "MySerivice"
let userAccount = "authenticatedUser"
let accessGroup = "MySerivice"

// Arguments for the keychain queries
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

class KeychainService: NSObject {

/**
* Exposed methods to perform queries.
* Note: feel free to play around with the arguments
* for these if you want to be able to customise the
* service identifier, user accounts, access groups, etc.
*/
internal class func saveToken(token: NSString) {
    self.save(serviceIdentifier, data: token)
}

internal class func loadToken() -> NSString? {
    var token = self.load(serviceIdentifier)

    return token
}

/**
* Internal methods for querying the keychain.
*/
private class 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: [kSecClassGenericPasswordValue, service, userAccount, dataFromString], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecValueDataValue])

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

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

private class func load(service: NSString) -> String? {
    // Instantiate a new default keychain query
    // Tell the query to return a result
    // Limit our results to one item
    var keychainQuery: NSMutableDictionary = NSMutableDictionary(objects: [kSecClassGenericPasswordValue, service, userAccount, kCFBooleanTrue, kSecMatchLimitOneValue], forKeys: [kSecClassValue, kSecAttrServiceValue, kSecAttrAccountValue, kSecReturnDataValue, kSecMatchLimitValue])

    var dataTypeRef :Unmanaged<AnyObject>?

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

    let opaque = dataTypeRef?.toOpaque()

    var contentsOfKeychain: String?

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

        // Convert the data retrieved from the keychain into a string
        contentsOfKeychain = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
    } else {
        println("Nothing was retrieved from the keychain. Status code \(status)")
    }

    return contentsOfKeychain
    }
}

I just wan't to remove the Optional thing around the str Or is there a better way to do that?

I have take this code from:

http://matthewpalmer.net/blog/2014/06/21/example-ios-keychain-swift-save-query/

Remains answered 25/8, 2014 at 14:27 Comment(6)
Please show the code that is causing the error including setup code. A complere self-contained example is really best.Joel
Zaph i updated the postErikaerikson
Wait im going to post the whole classErikaerikson
I just want to remove The "Optional()" around my stringErikaerikson
Which Beta are you using? On Beta6 I am getting an error for takeRetainedValue() and it also seems that it is not needed. let kSecClassValue = kSecClass as NSString works. Apple has been updating a lot of the APIs to work with Swift.Joel
I am using the beta 6 and i don't get any error exept when i try accessing a letter of the string using str[i]Erikaerikson
J
28

You get the Optional("") because the optional value is not unwrapped. You need to put a ! after the object and you won't get the Optional("") bit any more. I would show you the code but you haven't shown us the print() statement. I made some sample ones below that I think would replicate the problem, though I haven't tried them.

var value:String?
value = "Hello, World"

print("The Value Is \(value)") // Prints "The Value Is Optional(Hello, World)"
print("The Value Is \(value!)")// Prints "The Value Is Hello, World"

Im hoping this answers your question or at least points you in the right direction, just ask if you need more information or a better example.

Jiujitsu answered 25/8, 2014 at 15:37 Comment(2)
An 'if let' is really needed so there will not be a crash if loadToken fails. We should not just throw-in explicit unwrapping unless we are completely sure the Optional can never be nil.Joel
Quite right, and definitely worth bearing in mind when using optionals though i couldn't show a good example of how the code wasn't working otherwise.Jiujitsu
J
2

Here is a Swift 2 example implementation:

import Security

class ZLKeychainService: NSObject {

    var service = "Service"
    var keychainQuery :[NSString: AnyObject]! = nil

    func save(name name: NSString, value: NSString) -> OSStatus? {
        let statusAdd :OSStatus?

        guard let dataFromString: NSData = value.dataUsingEncoding(NSUTF8StringEncoding) else {
            return nil
        }

        keychainQuery = [
            kSecClass       : kSecClassGenericPassword,
            kSecAttrService : service,
            kSecAttrAccount : name,
            kSecValueData   : dataFromString]
        if keychainQuery == nil {
            return nil
        }

        SecItemDelete(keychainQuery as CFDictionaryRef)

        statusAdd = SecItemAdd(keychainQuery! as CFDictionaryRef, nil)

        return statusAdd;
    }

    func load(name name: NSString) -> String? {
        var contentsOfKeychain :String?

        keychainQuery = [
            kSecClass       : kSecClassGenericPassword,
            kSecAttrService : service,
            kSecAttrAccount : name,
            kSecReturnData  : kCFBooleanTrue,
            kSecMatchLimit  : kSecMatchLimitOne]
        if keychainQuery == nil {
            return nil
        }

        var dataTypeRef: AnyObject?
        let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)

        if (status == errSecSuccess) {
            let retrievedData: NSData? = dataTypeRef as? NSData
            if let result = NSString(data: retrievedData!, encoding: NSUTF8StringEncoding) {
                contentsOfKeychain = result as String
            }
        }
        else {
            print("Nothing was retrieved from the keychain. Status code \(status)")
        }

        return contentsOfKeychain
    }
}

//Test:
let userName = "TestUser"
let userValue: NSString = "TestValue"
print("userName: '\(userName)'")
print("userValue: '\(userValue)'")

let kcs = ZLKeychainService()

kcs.save(name:userName, value: userValue)
print("Keychain Query \(kcs.keychainQuery)")

if let recoveredToken = kcs.load(name:userName) {
    print("Recovered Value: '\(recoveredToken)'")
}

Output:

userName: 'TestUser'
userValue: 'TestValue'
Keychain Query [acct: TestUser, v_Data: <54657374 56616c75 65>, svce: Service, class: genp]
Recovered Value: 'TestValue'

Joel answered 25/8, 2014 at 15:44 Comment(1)
But thanks for the code, I had tried this on a very early Beta, it is a lot easier on Beta6.Joel
B
1

You can use the Swift wrapper over the Keychain C API, and avoid the above problems altogether. https://github.com/deniskr/KeychainSwiftAPI

Bourges answered 3/12, 2014 at 14:54 Comment(1)
Thanks but my problem has already been solved i only had to put an ! at the end of my variableErikaerikson
A
0

You will get the Optional("") because the optional value is not unwrapped and if you want to unwrap the optional value to get the string value, do

yourValue.unsafelyUnwrapped

Ambroseambrosi answered 29/6, 2018 at 6:11 Comment(0)
C
-2

You actually don't even need to do anything. The "Optional" string isn't in the actual data. That is just something Swift seems to place on the output on the console when it is an optional value that isn't unwrapped. IE The data itself doesn't contain the string Optional.

Still, good to unwrap it if you know it contains data.

Checkroom answered 13/10, 2014 at 3:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.