Storing multiple values in Keychain using Locksmith - swift

Can you store multiple key/value pairs under one forUserAccount?
try? Locksmith.saveData(["access_token" : access_token], forUserAccount: "UserData")
try? Locksmith.saveData(["email" : email!], forUserAccount: "UserData")
try? Locksmith.saveData(["markets" : market], forUserAccount: "UserData")
This code fails when looking for "email.". The only way I can do this is to create multiple forUserAccount strings: UserData, UserData1, UserData3, etc. I have tried updateData with no luck. If forUserAccount has to be unique, what is it for? It seems unnecessary if that's the case.

let userIDNumber = userID as NSNumber
let userIDString : String = userIDNumber.stringValue
let keychainData = [_keychainEmailKey: email, _keychainPasswordKey: password, _keychainUserIDKey: userIDString]
Locksmith.updateData(keychainData, forUserAccount: email)
Where _keychainEmailKey, etc are constants for keys. So forUserAccount should be unique, probably it is overriding previous values.

Just like #nyekimov's answer alludes to, a dictionary of values can be stored for an account. To clarify, here's a code snippet:
do
{
try Locksmith.saveData(["user": "notMyRealUsername", "password":"123456"], forUserAccount: "myUserAccount") //store it
let dictionary = Locksmith.loadDataForUserAccount("myUserAccount") //load it
print(dictionary) //let's see what we've got
}
catch
{
// handle errors here
}
Use try Locksmith.updateData(["user": "notMyRealUsernameEither", "password":"123456"], forUserAccount: "myUserAccount") when an account exists.

Related

Error when saving to keychain using SecItemAdd

I'm getting an error saving an encoded value to keychain at the point of SecItemAdd. I'm fairly new to working with Keychain and not sure how to return the error to see what I'm doing incorrectly.
let encoder = JSONEncoder()
func initiateLogin(forceReconnect: Bool = false, completion: #escaping (Bool)->Void) {
Task {
await loginUser(forceReconnect: forceReconnect, completion: { user in
if let encoded = try? self.encoder.encode(user) {
// MARK: - keychain
let attributes: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "johnDoe",
kSecValueData as String: encoded,
]
if SecItemAdd(attributes as CFDictionary, nil) == noErr {
print("\(#function) 😀 user saved successfully in keychain")
} else {
print("\(#function) ⚠️ something went wrong")
}
self.initClient(withCredentials: user)
completion(true)
}
})
}
}
You didn't specify which error you are getting (it should be a return value of SecItemAdd), but the most common mistake is this: as documentation states:
The operation might fail, for example, if an item with the given attributes already exists.
In other words: your code will only work once for each unique kSecAttrAccount.
Instead, you need to check if an item already exists, and if yes, update it (or delete the previous one and create a new one).
How to update the items or delete them is explained here.
Side note: it's also a good idea to put keychain management into a separate class (a wrapper), which you can call from anywhere in your code to save / load data from keychain. Here's a good tutorial on how to create such wrapper.

How to remove duplicate keys from dictionary?

I'm using Xcode 13.4. I'm working on a dictionary based app. I've an issue with dictionary that I've thousands of keys in dictionary. Now I've an error
Thread 1: Fatal error: Dictionary literal contains duplicate keys
How to get ride of this error? I've tried many answers from StackOverFlow but i can't get solution about my lengthy dictionary.
let dict : [String:String] = [
"ik4":"一",
"sioh8":"一",
"cik4": "七",
"ding1": "丁",
"diong7": "丈",
"diong5":"丈",
.........]
Each dictionary key MUST be unique. Its your responsibility if you are trying to create manually dictionary, Keys should be unique.
As such no api to remove duplicate keys from dictionary.
If it's important to check also the values of the duplicate keys you have to do it manually.
If not an easy way to remove the duplicate keys is to make the dictionary a JSON string and decode end re-encode the JSON.
Copy the entire dictionary literal and paste it into a Playground
Replace [] with {} and wrap the literal in """ like
let dict = """
{
"ik4":"一",
"sioh8":"一",
"cik4": "七",
"ding1": "丁",
"diong7": "丈",
"diong5":"丈",
"cik4": "七"
}
"""
Run this code
do {
let input = try JSONDecoder().decode([String:String].self, from: Data(dict.utf8))
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let output = try encoder.encode(input)
let string = String(data: output, encoding: .utf8)!
print(string)
} catch {
print(error)
}
Copy the result in the Console between {} and paste it into your project.

How to check if json has a key without SwiftyJSON in swiftUI?

I need to check a json response and find out if the response has a key.
I found other similar questions but they are working with libraries like SwiftyJSON.
I don't want to use that.
I need to know if its possible to do this without using any third party libs?
My current code is this:
let data = str.data(using: .utf8)!
do {
if let jsonArray = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary
{
print(jsonArray)
// I NEED TO DO CHECKS HERE...
// something like this:
if (jsonArray['someKey'] = exist){
do something here
}else{
do something else
}
}
}
Is this possible at all?
Try using:
if let value = jsonArray["someKey"] {
// If key exist, this code will be executed
} else {
// If key does not exist, this code will be executed
}

Value not being removed from dictionary

I have a dictionary in Firebase called peopleWhoLike, the key is an auto-id and the value is the uid of the user who liked, I'm trying to loop through the peopleWhoLike dictionary and find the entry with the current users uid as the value so that I can remove it, but the value is not being removed.
func removeLike(postID: String){
ref.child("posts").child(postID).observe(.value, with: { (snapshot) in
if let info = snapshot.value as? [String : AnyObject]{
if var peopleWhoLike = info["peopleWhoLike"] as? [String : String]{
print("peopleWhoLike - \(peopleWhoLike)")
for person in peopleWhoLike{
if person.value == FIRAuth.auth()!.currentUser!.uid{
peopleWhoLike.removeValue(forKey: person.key)
print("personkey - \(person.key)")
}
}
}
}
})
}
Both print statements print correctly, ie person.key being the correct key
Any help would be much appreciated thanks!
you have only an snapshot (a copy) of the data there
to remove the value from the firebase database;
try this:
//path should be like this (i guess):
let currentUserUid = FIRAuth.auth()!.c‌​urrentUser!.uid
ref.child("posts")
.child(postId)
.child("peopleWhoLike")
.chi‌​ld(currentUserUid)
.rem‌​oveValue()
or same:
let currentUserUid = FIRAuth.auth()!.c‌​urrentUser!.uid
ref.child("posts/\(postId)/peopleWhoLike/\(currentUserUid)").rem‌​oveValue()
UPDATE
you you like to remove the person key - then you can:
a) iterate over peopleWhoLike and find if it is the user ( but please put this let currentUserUid = FIRAuth.auth()!.c‌​urrentUser!.uid outside the loop!
//path should be like this (i guess):
let currentUserUid = FIRAuth.auth()!.c‌​urrentUser!.uid
// loop and when match `person.value == currentUserUid` then:
ref.child("posts")
.child(postId)
.child("peopleWhoLike")
.chi‌​ld(person.key) //<-- change here
.rem‌​oveValue()
b) you search in the query. and remove then the resulting node.
ref.child("posts")
.child(postId)
.child("peopleWhoLike")
.startAt(currentUserId)
.endAt(currentUserId)
. [...] do something
i dont know if you can direct call .removeValue() at this point. but with a SingleEvent and an snapshot you can do snapshot.ref.removeValue() - doublecheck before you delete. but since this results in a reference you should direct able to call .removeValue()
ref.child("posts")
.child(postId)
.child("peopleWhoLike")
.startAt(currentUserId)
.endAt(currentUserId)
.removeValue()
note: this search takes longer than a direct path
see here doc for query:
https://firebase.googleblog.com/2013/10/queries-part-1-common-sql-queries.html#byemail
https://firebase.google.com/docs/database/ios/read-and-write#delete_data
NOTE:
i would advice you to save it with the userUid as key because you only need a onliner to delete (see my first codesnippet where you not need to get all data from peopleWhoLike) and as value just 1 or you can save the current date (then you know when it was liked)

Syntax about try and "as?" "as!"

I read a snippet from this POST but have not quite understood.
https://www.hackingwithswift.com/example-code/system/how-to-parse-json-using-nsjsonserialization
I am confused on some syntax in the following snippets.
In the following, I am not knowing why try goes here, it is strange syntax to me. Any information about try and as! for this expression?
let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! [String: AnyObject]
In the following, I am not knowing what is as? [String] doing?
if let names = json["names"] as? [String] {
I know this question might be fundamental, I just need a keyword for me to search the related anwser, thanks. Here is my whole code block.
// define a string
let str = "{\"names\": [\"Bob\", \"Tim\", \"Tina\"]}"
// convert the string into NSUTF8StringEncoding
let data = str.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
// put the following statements in try catch block
do {
// not knowing why try goes here, strange syntax to me.
// any higher conception about try and as! for this expression
let json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! [String: AnyObject]
// json should be an object(dictionary, hash) use the key "names" to store string array into names
// not knowing what is `as? [String]` doing? any keyword for this syntax?
if let names = json["names"] as? [String] {
print(names)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
// not knowing why try goes here, strange syntax to me. what if it fails? as! is what syntax, any keyword to search for it?
The try here is to basically to try to perform the function on that line. If it fails it will go to the catch. Which in this case will just print("Failed to load: \(error.localizedDescription)")
// json should be an object(dictionary, hash) use the key "names" to store string array into names // not knowing what is as? [String] doing? any keyword for this syntax?
The line that you are confuse about is performing a if else on the json object. It check for a key that is name and also want to ensure that its value is a String therefore the as?. In this case if the key name does not exist it will not met the condition. If the value of name is not a String it will not met the condition.
Like what #Martin R mention in the comments, you should read up more on Type Casting in Swift. This should help you. Explanation with some example if you have trouble with Apple Documentation.
As for try catch it is actually use in other languages as well not just Swift. You can read up more here.