Saving Alamofire request data into a variable - swift

I'm having troubles with getting password salt from my VM server via Alamofire. I'm making a request to server and it should return me salt, so I can salt my password, hash it and send it back to server.
The problem is that I do not understand how to save the salt, that Alamofire receives, into a variable, so I can just add it to password and hash it like that:
let salted_password = user_password + salt
let hash = salted_password.sha1()
Where user_password is what the user entered into password field and salt is what I got from Alamofire salt request.
Here is my code:
func getSalt(completionHandler: #escaping (DataResponse<String>, Error?) -> Void) {
Alamofire.request("http://192.168.0.201/salt", method: .post, parameters: salt_parameters).responseString { response in
switch response.result {
case .success(let value):
completionHandler(response as DataResponse<String>, nil)
case .failure(let error):
completionHandler("Failure", error)
}
}
}
let salt = getSalt { response, responseError in
return response.result.value!
}
It gives me the following error:
Binary operator '+' cannot be applied to operands of type 'String' and '()'.
So is it possible to save a request value into a variable? What should I do?
Thank you for your attention.

The problem here is because of how you implemented your completion block
For example:
func someAsynchronousCall(a: Int, b: Int, #escaping block: (_ result: Int) -> Void) {
... some code here
... {
// let's just say some async call was done and this code is called after the call was done
block(a + b)
}
}
To use this code it would look like this:
var answer: Int = 0
someAsynchronousCall(100, b: 200) { result in // the `result` is like what you're returning to the user since API calls need to be called asynchronously you do it like this rather than creating a function that has a default return type.
answer = result
print(answer)
}
print(answer)
The print would look like this
0
300
Since we declared answer as 0 it printed that first since the async call wasn't done yet, after the async call was done (usually a few milliseconds after) it then printed 300
So all in all your code should look something like this
var salt: String?
getSalt { response, responseError in
salt = response.result.value
}

Related

Parsing Alamofire JSON

I am building an app that will work with Plaid. Plaid provides a nice little LinkKit that I need to use to grab my link_token. I provide that link_token to authenticate to a bank. I have written a request using Alamofire to send the .post to get the new link_token when someone would want to add another account. My issue is when I decode the JSON to a struct that I have built I cant seem to use that stored link_token value.
Code to retrieve link_token
let parameters = PlaidAPIKeys(client_id: K.plaidCreds.client_id,
secret: K.plaidCreds.secret,
client_name: K.plaidCreds.client_name,
language: K.plaidCreds.language,
country_codes: [K.plaidCreds.country_codes],
user: [K.plaidCreds.client_user_id: K.plaidCreds.unique_user_id],
products: [K.plaidCreds.products])
func getLinkToken() {
let linkTokenRequest = AF.request(K.plaidCreds.plaidLinkTokenURL,
method: .post,
parameters: parameters,
encoder: JSONParameterEncoder.default).responseDecodable(of: GeneratedLinkToken.self) { response in
print(response)
}
}
Struct I have built:
struct GeneratedLinkToken: Decodable {
let expiration: String
let linkToken: String
let requestID: String
enum CodingKeys: String, CodingKey {
case expiration = "expiration"
case linkToken = "link_token"
case requestID = "request_id"
}
}
I have tested by calling the function getLinkToken() when pressing my add account or dummy button, I do get the data back that I am needing. Why wouldnt I be able to access GeneratedLinkToken.linkToken directly after the request?
You aren't able to access linkToken property like this: GeneratedLinkToken.linkToken, because linkToken is as instance property(read here)
If you want to get an instance after your request, you can do it like this:
func getLinkToken(completion: #escaping ((GeneratedLinkToken) -> Void)) {
let linkTokenRequest = AF.request(K.plaidCreds.plaidLinkTokenURL,
method: .post,
parameters: parameters,
encoder: JSONParameterEncoder.default).responseDecodable(of: GeneratedLinkToken.self) { response in
print(response)
// if response is an object of type GeneratedLinkToken
switch response.result {
case .success(let object):
completion(object)
case .failure(let error):
// hanlde error
}
}
}
Later you can call as:
getLinkToken { linkObject in
print("My tokne: \(linkObject.linkToken)")
}
I added completion(read here) to your method, since the request executing async, you can take a look at this Q/A: read here. I also suggest you, pass parameters as a parameter to this function, not declare it globally.

How to correct the order of execution of code in Swift 5?

The code within the function is executed in a different order than it is expected. I wanted to change the state of the login Boolean variable inside the if statement, but the function returns the initial value before if statement is completed.
Code sample:
class ClassName {
func loginRequest (name: String, pwd: String) -> Bool {
var login:Bool
//Initial value for login
login = false
let task = session.uploadTask(with: request, from: jsonData) { data, response, error in
if let httpResponse = response as? HTTPURLResponse {
print(httpResponse.statusCode)
if (httpResponse.statusCode) == 200 {
//Change the value of login if login is successful
login = true
if let data = data, let dataString = String(data: data, encoding: .utf8) {
do {
...
} catch {print(error.localizedDescription)}
}
}
}
}
task.resume()
//Problem return false in any case because return is completed before if statement
return login
}
}
Completion Handlers is your friend
The moment your code runs task.resume(), it will run your uploadTask and only when that function is finished running it will run the code where you change your login variable.
With That said: That piece of code is running asynchronously. That means your return login line of code won't wait for your network request to come back before it runs.
Your code is actually running in the order it should. But i myself wrote my first network call like that and had the same problem. Completion Handles is how i fixed it
Here is a very nice tutorial on Completion Handlers or you might know it as Callbacks :
Link To Completion Handlers Tutorial
If i can give you a little hint - You will have to change your function so it looks something like this: func loginRequest (name: String, pwd: String, completionHandler: #escaping (Bool) -> Void)
And replace this login = true with completionHandler(true)
Wherever it is you call your function it will look something like this:
loginRequest(name: String, pwd: String) {didLogIn in
print("Logged In : \(didLogIn)")
}
One last thing... You're actually already using Completion Handlers in your code.
let task = session.uploadTask(with: request, from: jsonData) { data, response, error in
... ... But hopefully now you understand a little bit better, and will use a completion handler approach when making network calls.
GOOD LUCK !

Swift Response template without additional casting

Do somebody know how to achieve Template on Response but without extra casting? Now if I do so Xcode returns me error that I can't override T dynamically. But I really believe I on right way but missed something. No?
Now it's looks like: func didReciveResponse(request: Request, response: Response<Any>)
enum Response<T> {
case success(response: T)
case failured(error: Error)
}
func pseudoResponse() {
let time: Timeinterval = 3
// somehow cast T (compiler shows error that I can't do this)
let response = .success<Timeinterval>(time)
didReciveResponse(.time, response)
}
// delegate method
func didReciveResponse(request: Request, response: Response) {
switch request {
case .time:
switch response {
// response without additional casting (as?)
case .success(let value): time = value
}
}
}
As the compiler cannot infer the generic type you have to annotate the type in this case
func pseudoResponse() {
let time: TimeInterval = 3.0
let response : Response<TimeInterval> = .success(response: time)
didReciveResponse(request: .time, response: response)
}
and you have to specify the Response type in the delegate method
func didReciveResponse(request: Request, response: Response<TimeInterval>) { ...
However if you want to make didReciveResponse (actually didReceiveResponse) also generic you need to write
func didReciveResponse<T>(request: Request, response: Response<T>) {

Adding a completion handler to Alamofire

I'm working on a small chunk of Swift code to work with pulling data via web-based APIs. Right now I am close, but think I'm missing the completion handler aspect when I print the data within the getUserInfo() expected data is there, but outside that function, the initialized default data appears. The function is called like this:
print("Provided Username is: \(workingData.user)")
getUserInfo()
print("Returned String Data is: \(workingData.responseDataString)")
and the actual function:
func getUserInfo() {
Alamofire.request(workingjss.jssURL + devAPIMatchPath + workingData.user, method: .get)
.authenticate(user: workingjss.jssUsername, password: workingjss.jssPassword).responseString { response in
if (response.result.isSuccess) {
print("In Function Data: \(response.result.value!)"
workingData.responseDataString = response.result.value!
}
}
}
The output in running the code is:
Provided Username is: MYUSER
Returned String Data is: Nothing Here Yet
In Function Data: {"Cleaned JSON Data here"}
Would a completion handler help the issue out? I'm pretty new to working with Alamofire so sorry if this is an easy one. Thanks!
Try using a completion handler:
func getUserInfo(completion: #escaping (String) -> Void) {
Alamofire.request(workingjss.jssURL + devAPIMatchPath + workingData.user, method: .get)
.authenticate(user: workingjss.jssUsername, password: workingjss.jssPassword).responseString { response in
if (response.result.isSuccess) {
print("In Function Data: \(response.result.value!)"
completion(response.result.value!)
}
}
}
And call it like:
getUserInfo() { response in
// Do your stuff here
workingData.responseDataString = response
print("Returned String Data is: \(workingData.responseDataString)")
}

AlamoFire GET api request not working as expected

I am trying to get learn how to use AlamoFire and I am having trouble.
My method so far is as follows:
func siteInfo()->String?{
var info:NSDictionary!
var str:String!
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {(request, response, JSON, error) in
info = JSON as NSDictionary
str = info["access_key"] as String
//return str
}
return str
}
This returns nil which is a problem. From what I have read here, this is because the request can take a while so the closure doesn't execute till after the return. The suggested solution of moving the return into the closure does not work for me and the compiler just yells (adding ->String after (request,response,JSON,error) which gives "'String' is not a subtype of void"). Same goes for the other solution provided.
Any ideas? Even some source code that is not related to this problem, that uses AlamoFire, would be helpful.
Thanks!
One way to handle this is to pass a closure (I usually call it a completionHandler) to your siteInfo function and call that inside Alamofire.request's closure:
func siteInfo(completionHandler: (String?, NSError?) -> ()) -> () {
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {
(request, response, JSON, error) in
let info = JSON as? NSDictionary // info will be nil if it's not an NSDictionary
let str = info?["access_key"] as? String // str will be nil if info is nil or the value for "access_key" is not a String
completionHandler(str, error)
}
}
Then call it like this (don't forget error handling):
siteInfo { (str, error) in
if str != nil {
// Use str value
} else {
// Handle error / nil value
}
}
In the comments you asked:
So how would you save the info you collect from the get request if you
can only do stuff inside the closure and not effect objects outside of
the closure? Also, how to keep track to know when the request has
finished?
You can save the result of the get request to an instance variable in your class from inside the closure; there's nothing about the closure stopping you from doing that. What you do from there really depends on, well, what you want to do with that data.
How about an example?
Since it looks like you're getting an access key form that get request, maybe you need that for future requests made in other functions.
In that case, you can do something like this:
Note: Asynchronous programming is a huge topic; way too much to cover here. This is just one example of how you might handle the data you get back from your asynchronous request.
public class Site {
private var _accessKey: String?
private func getAccessKey(completionHandler: (String?, NSError?) -> ()) -> () {
// If we already have an access key, call the completion handler with it immediately
if let accessKey = self._accessKey {
completionHandler(accessKey, nil)
} else { // Otherwise request one
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {
(request, response, JSON, error) in
let info = JSON as? NSDictionary // info will be nil if it's not an NSDictionary
let accessKey = info?["access_key"] as? String // accessKey will be nil if info is nil or the value for "access_key" is not a String
self._accessKey = accessKey
completionHandler(accessKey, error)
}
}
}
public func somethingNeedingAccessKey() {
getAccessKey { (accessKey, error) in
if accessKey != nil {
// Use accessKey however you'd like here
println(accessKey)
} else {
// Handle error / nil accessKey here
}
}
}
}
With that setup, calling somethingNeedingAccessKey() the first time will trigger a request to get the access key. Any calls to somethingNeedingAccessKey() after that will use the value already stored in self._accessKey. If you do the rest of somethingNeedingAccessKey's work inside the closure being passed to getAccessKey, you can be sure that your accessKey will always be valid. If you need another function that needs accessKey, just write it the same way somethingNeedingAccessKey is written.
public func somethingElse() {
getAccessKey { (accessKey, error) in
if accessKey != nil {
// Do something else with accessKey
} else {
// Handle nil accessKey / error here
}
}
}