Trying to use KeychainItemWrapper by Apple "translated" to Swift - swift

Sigh, I have been working on this the whole afternoon... here is my nightmare:
I am trying to use the KeychainItemWrapper made by Apple. But I "translated" its Objective-C codes to Swift:
import Foundation
import Security
class MyKeychainItemWrapper: NSObject {
var keychainItemData: NSMutableDictionary?
var genericPasswordQuery: NSMutableDictionary = NSMutableDictionary()
init(identifier: String, accessGroup: String?) {
super.init()
// Begin Keychain search setup. The genericPasswordQuery leverages the special user
// defined attribute kSecAttrGeneric to distinguish itself between other generic Keychain
// items which may be included by the same application.
genericPasswordQuery.setObject(kSecClassGenericPassword, forKey: kSecClass)
genericPasswordQuery.setObject(identifier, forKey: kSecAttrGeneric)
// The keychain access group attribute determines if this item can be shared
// amongst multiple apps whose code signing entitlements contain the same keychain access group.
println(accessGroup)
if (!(accessGroup == nil)) {
genericPasswordQuery.setObject(accessGroup!, forKey: kSecAttrAccessGroup)
}
// Use the proper search constants, return only the attributes of the first match.
genericPasswordQuery.setObject(kSecMatchLimitOne, forKey: kSecMatchLimit)
genericPasswordQuery.setObject(kCFBooleanTrue, forKey: kSecReturnAttributes)
var tempQuery: NSDictionary = NSDictionary(dictionary: genericPasswordQuery)
var outDictionary: Unmanaged<AnyObject>? = nil
var status: OSStatus = SecItemCopyMatching(tempQuery as CFDictionaryRef, &outDictionary)
println(status == noErr)
if (status == noErr) {
// Stick these default values into keychain item if nothing found.
resetKeychainItem()
// Add the generic attribute and the keychain access group.
keychainItemData!.setObject(identifier, forKey: kSecAttrGeneric)
if (!(accessGroup == nil)) {
keychainItemData!.setObject(accessGroup!, forKey: kSecAttrAccessGroup)
}
} else {
// load the saved data from Keychain.
keychainItemData = secItemFormatToDictionary(outDictionary?.takeRetainedValue() as NSDictionary)
}
}
Then in my app's AppDelegate.swift, I am trying to use it by:
import UIKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var passwordItem: MyKeychainItemWrapper = MyKeychainItemWrapper(identifier: "Password", accessGroup: nil)
...
So, the initializer is called, but but but somehow, I ALWAYS, ALWAYS get
Thread 1: EXC_BREAKPOINT (code=EXC_ARM_BREAKPOINT, subcode=0xe7ffdefe)
I have tried commenting out the problem lines and then I get this error at another if():
I even tried:
var mmm: Bool = (accessGroup == nil)
if (!mmm) {
genericPasswordQuery.setObject(accessGroup!, forKey: kSecAttrAccessGroup)
}
But same error at the same place, i.e. if(..)
I am now so confused. Did I miss something here or?
Environment: Xcode6-beta6, iOS 8 beta 5 on a non-jailbroken iPhone 5.

Swift 3
import UIKit
import Security
let kSecClassGenericPasswordValue = String(format: kSecClassGenericPassword as String)
let kSecClassValue = String(format: kSecClass as String)
let kSecAttrServiceValue = String(format: kSecAttrService as String)
let kSecValueDataValue = String(format: kSecValueData as String)
let kSecMatchLimitValue = String(format: kSecMatchLimit as String)
let kSecReturnDataValue = String(format: kSecReturnData as String)
let kSecMatchLimitOneValue = String(format: kSecMatchLimitOne as String)
let kSecAttrAccountValue = String(format: kSecAttrAccount as String)
struct KeychainAccess {
func setPasscode(identifier: String, passcode: String) {
if let dataFromString = passcode.data(using: String.Encoding.utf8) {
let keychainQuery = [
kSecClassValue: kSecClassGenericPasswordValue,
kSecAttrServiceValue: identifier,
kSecValueDataValue: dataFromString
] as CFDictionary
SecItemDelete(keychainQuery)
print(SecItemAdd(keychainQuery, nil))
}
}
func getPasscode(identifier: String) -> String? {
let keychainQuery = [
kSecClassValue: kSecClassGenericPasswordValue,
kSecAttrServiceValue: identifier,
kSecReturnDataValue: kCFBooleanTrue,
kSecMatchLimitValue: kSecMatchLimitOneValue
] as CFDictionary
var dataTypeRef: AnyObject?
let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
var passcode: String?
if (status == errSecSuccess) {
if let retrievedData = dataTypeRef as? Data,
let result = String(data: retrievedData, encoding: String.Encoding.utf8) {
passcode = result as String
}
}
else {
print("Nothing was retrieved from the keychain. Status code \(status)")
}
return passcode
}
}
Swift 2
import UIKit;
import Security;
let kSecClassGenericPasswordValue = NSString(format: kSecClassGenericPassword);
let kSecClassValue = NSString(format: kSecClass);
let kSecAttrServiceValue = NSString(format: kSecAttrService);
let kSecValueDataValue = NSString(format: kSecValueData);
let kSecMatchLimitValue = NSString(format: kSecMatchLimit);
let kSecReturnDataValue = NSString(format: kSecReturnData);
let kSecMatchLimitOneValue = NSString(format: kSecMatchLimitOne);
let kSecAttrAccountValue = NSString(format: kSecAttrAccount);
class KeychainAccess: NSObject {
func setPasscode(identifier: String, passcode: String) {
let dataFromString: NSData = passcode.dataUsingEncoding(NSUTF8StringEncoding)!;
let keychainQuery = NSDictionary(
objects: [kSecClassGenericPasswordValue, identifier, dataFromString],
forKeys: [kSecClassValue, kSecAttrServiceValue, kSecValueDataValue]);
SecItemDelete(keychainQuery as CFDictionaryRef);
let status: OSStatus = SecItemAdd(keychainQuery as CFDictionaryRef, nil);
}
func getPasscode(identifier: String) -> NSString? {
let keychainQuery = NSDictionary(
objects: [kSecClassGenericPasswordValue, identifier, kCFBooleanTrue, kSecMatchLimitOneValue],
forKeys: [kSecClassValue, kSecAttrServiceValue, kSecReturnDataValue, kSecMatchLimitValue]);
var dataTypeRef: AnyObject?
let status: OSStatus = SecItemCopyMatching(keychainQuery, &dataTypeRef)
var passcode: NSString?;
if (status == errSecSuccess) {
let retrievedData: NSData? = dataTypeRef as? NSData
if let result = NSString(data: retrievedData!, encoding: NSUTF8StringEncoding) {
passcode = result as String
}
}
else {
print("Nothing was retrieved from the keychain. Status code \(status)")
}
return passcode;
}
}
Then from anywhere simply call:
func setPasscode(passcode: String) {
let keychainAccess = KeychainAccess();
keychainAccess.setPasscode("YourAppIdentifier", passcode:passcode);
}
func getPasscode() -> NSString {
let keychainAccess = KeychainAccess();
return keychainAccess.getPasscode("YourAppIdentifier")!;
}
func deletePasscode() {
let keychainAccess = KeychainAccess();
keychainAccess.setPasscode("YourAppIdentifier", passcode:"");
}

official is GenericKeychain
existing several swift version, the best one is:
jrendel/SwiftKeychainWrapper ยท GitHub
how to use it:
download file: KeychainWrapper.swift
write code to set/get/delete:
let StrUsernameKey:String = "username"
let StrPasswordKey:String = "password"
let saveSuccessful: Bool = KeychainWrapper.setString(usernameTextField.text!, forKey: StrUsernameKey)
print("saveSuccessful=\(saveSuccessful)") //saveSuccessful=true
let retrievedString: String? = KeychainWrapper.stringForKey(StrUsernameKey)
print("retrievedString=\(retrievedString)") //retrievedString=Optional("yourLastStoredUsernameString")
let removeSuccessful: Bool = KeychainWrapper.removeObjectForKey(StrUsernameKey)
print("removeSuccessful=\(removeSuccessful)") //removeSuccessful=true
let retrievedStringAfterDelete: String? = KeychainWrapper.stringForKey(StrUsernameKey)
print("retrievedStringAfterDelete=\(retrievedStringAfterDelete)") //retrievedStringAfterDelete=nil

Updates for Swift 2.
Here is an example implementation that may help.:
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'

My solution seems working:
init(identifier: String) {
super.init()
genericPasswordQuery.setObject(kSecClassGenericPassword, forKey: kSecClass as String)
genericPasswordQuery.setObject(identifier, forKey: kSecAttrGeneric as String)
// Use the proper search constants, return only the attributes of the first match.
genericPasswordQuery.setObject(kSecMatchLimitOne, forKey: kSecMatchLimit as String)
genericPasswordQuery.setObject(kCFBooleanTrue, forKey: kSecReturnAttributes as String)
var tempQuery: NSDictionary = NSDictionary(dictionary: genericPasswordQuery)
var outDictionary: Unmanaged<AnyObject>? = nil
let status: OSStatus = SecItemCopyMatching(tempQuery as CFDictionaryRef, &outDictionary)
var result: NSDictionary? = outDictionary?.takeRetainedValue() as NSDictionary?
if (result == nil) {
// Stick these default values into keychain item if nothing found.
resetKeychainItem()
// Add the generic attribute and the keychain access group.
keychainItemData!.setObject(identifier, forKey: kSecAttrGeneric as String)
} else {
// load the saved data from Keychain.
keychainItemData = secItemFormatToDictionary(result!)
}
}
The only thing I did is to unwrap the outDictionary immediately after getting it.

Related

How to store and retrieve data in userdefault? (Ex:Array of Struct )

struct ExpandableNames:Codable {
var isExpanded: Bool
var names: [String]
var icons: [String]
}
var names = ["Food/Drink", "Tours", "Transport", "Gifts","Flights", "Shopping", "Activities","Entertainment","Accomodation","Other"]
var icons = ["Food_Category", "Tours", "Transport_Category", "Gifts_Category","Flights_Category","Shopping_Category","Activities_category","Entertainment_category", "Accomodation_Category","Other_Category"]
var categoryWholeArray = [Int:ExpandableNames]()
How to store categoryWholeArray to user default?
I tried
Store userDefault:
UserDefaults.standard.set(try? PropertyListEncoder().encode(categoryWholeArray), forKey:"categoryWholeArray")
Retrieve data problem is here
if let data = UserDefaults.standard.value(forKey:"categoryWholeArray") as? Data {
let songs2 = try? PropertyListDecoder().decode(Array<categoryWholeArray.values>.self, from: data)
}
Anyone tried?
You need to encode data while storing in UserDefaults and when you try to get those data you must need to decode it.
Here is extension i have that might help you.
extension UserDefaults {
func setCustomObjToUserDefaults(CustomeObj: AnyObject, forKey:String) {
let defaults = UserDefaults.standard
let encodedData = NSKeyedArchiver.archivedData(withRootObject: CustomeObj)
defaults.set(encodedData, forKey: forKey)
defaults.synchronize()
}
func getCustomObjFromUserDefaults(forKey:String) -> AnyObject? {
let defaults = UserDefaults.standard
if defaults.object(forKey: forKey) != nil {
if let decoded = defaults.object(forKey: forKey) as? Data {
let decodedTeams = NSKeyedUnarchiver.unarchiveObject(with:decoded) as AnyObject
return decodedTeams
}
}
return nil
}
func setJsonObject<T: Encodable>(encodable: T, forKey key: String) {
if let data = try? JSONEncoder().encode(encodable) {
set(data, forKey: key)
}
}
func getJsonObject<T: Decodable>(_ type: T.Type, forKey key: String) -> T? {
if let data = object(forKey: key) as? Data,
let value = try? JSONDecoder().decode(type, from: data) {
return value
}
return nil
}
func removeCustomObject(forKey:String)
{
let defaults = UserDefaults.standard
defaults.removeObject(forKey: forKey)
defaults.synchronize()
}
}
All correct except
if let data = UserDefaults.standard.value(forKey:"categoryWholeArray") as? Data {
let songs2 = try? PropertyListDecoder().decode([Int:ExpandableNames].self, from: data)
print(songs2)
}

How do I cast __NSCFDictionary to 'NSArray in Swift 3.0

I have recently been updating a library to swift 3.0 as a relatively new swift dev. I get a error where I cannot cast __NSCFDictionary to a NSArray format. How could I convert __NSCFDictionary to NSArray format in swift with a example please?
This is the current function I am converting
method(name: "login", params: NSArray(arrayLiteral: params)) //fails casting here
guard let e = error , (e.isValid == true) else {
if let user = params["user"] as? AnyObject {
if let email = user["email"] {
self.userData.set(email, forKey: DDP_EMAIL)
}
if let username = user["username"] {
self.userData.set(username, forKey: DDP_USERNAME)
}
}
if let data = result as? NSDictionary,
let id = data["id"] as? String,
let token = data["token"] as? String,
let tokenExpires = data["tokenExpires"] as? NSDictionary {
let expiration = dateFromTimestamp(containedIn: tokenExpires)
self.userData.set(id, forKey: DDP_ID)
self.userData.set(token, forKey: DDP_TOKEN)
self.userData.set(expiration, forKey: DDP_TOKEN_EXPIRES)
}
self.userMainQueue.addOperation() {
if let c = callback { c(result, error) }
self.userData.set(true, forKey: DDP_LOGGED_IN)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: DDP_USER_DID_LOGIN), object: nil)
if let _ = self.delegate {
self.delegate!.ddpUserDidLogin(user: self.user()!)
}
}
return
}
log.debug("Login error: \(e)")
if let c = callback { c(result, error) }
}

Casting AnyObject to Specific Class

I'm using socket.io Swift Library. With the following line of code,
socket.on("info") { (dataArray, socketAck) -> Void in
let user = dataArray[0] as? User
print(user._id)
}
dataArray[0] is a valid object but user appears to be nil after casting.
Since dataArray[0] returns as an AnyObject,
how can i cast AnyObject to User Object?. Or somehow manage to do what i want with a different approach?
Since after this line
let user = dataArray[0] as? User
you have a nil value inside user it means that you don't have a User value at the first position of dataArray.
Since dataArray comes from a server (as I guess) it probably contains a serialized version of User.
Now we really need to know what really dataArray[0] is. However...
if dataArray[0] contains NSData
In this case try this
let json = JSON(dataArray[0] as! NSData)
let user = User(json:json)
You need to create a constructor that accept AnyObject and read data in it.
I guess in this case dataArray[0] is an JSON Object.
class User {
init(data: [String: AnyObject]) {
username = data["username"] as? String ?? ""
}
}
This is how i manage mine:
// Structure used as parameter
struct InfoStruct {
var nome: String = ""
var sobrenome:String = ""
var nascimentoTimestamp: NSNumber = 0
init() {
}
// Struct to AnyObject
func toAnyObject() -> Any {
var dic = [String:AnyObject?]()
if (nome != "") { dic["nome"] = nome as AnyObject }
if (sobrenome != "") { dic["sobrenome"] = sobrenome as AnyObject }
if (nascimentoTimestamp != 0) { dic["nascimentoTimestamp"] = nascimentoTimestamp as AnyObject }
return dic
}
// AnyObject to Struct
func fromAnyObject(dic:[String:AnyObject]) -> InfoStruct {
var retorno = InfoStruct()
if (dic["nome"] != nil) { retorno.nome = dic["nome"] as? String ?? "" }
if (dic["sobrenome"] != nil) { retorno.sobrenome = dic["sobrenome"] as? String ?? "" }
if (dic["nascimentoTimestamp"] != nil) { retorno.nascimentoTimestamp = dic["nascimentoTimestamp"] as? NSNumber ?? 0 }
return retorno
} }
// User class
class Usuario: NSObject {
var key: String
var admin: Bool
var info: InfoStruct // Struct parameter
init(_ key: String?) {
self.key = key ?? ""
admin = false
info = InfoStruct() // Initializing struct
}
// From Class to AnyObject
func toAnyObject() -> Any {
var dic = [String:AnyObject?]()
if (key != "") { dic["key"] = key as AnyObject }
if (admin != false) { dic["admin"] = admin as AnyObject }
dic["info"] = info.toAnyObject() as AnyObject // Struct
return dic
}
// From AnyObject to Class
func fromAnyObject(dic:[String:AnyObject]) -> Usuario {
let retorno = Usuario(dic["key"] as? String)
if (dic["key"] != nil) { retorno.key = dic["key"] as? String ?? "" }
if (dic["admin"] != nil) { retorno.admin = dic["admin"] as? Bool ?? false }
if (dic["info"] != nil) { retorno.info = InfoStruct.init().fromAnyObject(dic: dic["info"] as! [String : AnyObject]) } // Struct
return retorno
} }
// Using
let dao = FirebaseDAO.init(_type: FirebaseDAOType.firebaseDAOTypeUser)
dao.loadValue(key: uid) { (error, values:[String : AnyObject]) in
if error == nil {
let user = Usuario(values["key"] as? String).fromAnyObject(dic: values)
}
}
I hope it helps!

'if (let keychain = KeychainService.loadToken())' giving me a Expected declaration error

My app is crashing cause it is finding nil when unwrapping:
var keyChain:String = KeychainService.loadToken()!
So i try to do:
if (let keyChain = KeychainService.loadToken()){
}
But it gives me the error: Expected declaration
Is this the right way to do it? if not what am i doing wrong?
In case you wan't to see my KeychainService class:
// Identifiers
let serviceIdentifier = "serviceIndentifier"
let userAccount = "userAccount"
let accessGroup = "accessGroup"
// Arguments for the keychain queries
let kSecClassValue = kSecClass as NSString
let kSecAttrAccountValue = kSecAttrAccount as NSString
let kSecValueDataValue = kSecValueData as NSString
let kSecClassGenericPasswordValue = kSecClassGenericPassword as NSString
let kSecAttrServiceValue = kSecAttrService as NSString
let kSecMatchLimitValue = kSecMatchLimit as NSString
let kSecReturnDataValue = kSecReturnData as NSString
let kSecMatchLimitOneValue = kSecMatchLimitOne 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) -> NSString? {
// 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: NSString?
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
}
}
Omit the braces:
if let keyChain = KeychainService.loadToken() {
...
}

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

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/
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.
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'
You can use the Swift wrapper over the Keychain C API, and avoid the above problems altogether.
https://github.com/deniskr/KeychainSwiftAPI
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
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.