I'm using alamofire to get HTTP response from web api. just want to ask how can parse JSON values from response without using swift object mapper frameworks and pass it to authenticatedUser.I have some problem with loadUserData because response return Response type and my function work fine with NSArray how can I converted to work with Response
func GetUserCredential(username:String,password:String)->UserModel
{
let authenticatedUser = UserModel()
let user = username
let password = password
let credential = NSURLCredential(user: user, password: password, persistence: .ForSession)
Alamofire.request(.GET, "https://httpbin.org/basic-auth/\(user)/\(password)")
.authenticate(usingCredential: credential)
.responseJSON { response in
return self.loadUserData(response);
}
return authenticatedUser;
}
func loadUserData(response: Response<AnyObject, NSError>) -> UserModel
{
var userObj = UserModel()
for resp as? Response<AnyObject, NSError> in response
{
let user:String = (resp["user"] as! String)
let isAuthenticated:Bool = (resp["authenticated"] as! Bool)
let isManager:Bool = true
userObj = UserModel(username:user,isAuthenticated:isAuthenticated,isManager:isManager)
}
return userObj
}
I believe you can do this with an Alamofire response object.
if let validResponse = response.result.value as? [String : AnyObject] {
return loadUserData(validResponse);
}
I would change your loadUserData to this:
func loadUserData(response: [String : AnyObject]) -> UserModel
// Do everything you're doing in loadUserData
}
I would also do more error checking in your loadUserData method as it's best not to assume that user and authenticated will always exist (your app will crash if they don't or if their values are of different types).
I would also suggest you name GetUserCredential with a lowercase getUserCredential to keep in best practice for naming functions.
Related
Once the token is received, when the token is over, then how can I call the token automatically after the login? on same page
Alamofire.request(urlString, method: .post, parameters: newPost, encoding: JSONEncoding.default)
.responseJSON { response in
if let json = response.result.value as? [String : Any]{
print("JSON: \(json)")
if UserDefaults.standard.bool(forKey: "logged_in") {
Token = json["Token"]! as! String
UserDefaults.standard.set(Token, forKey: "Token")
UserDefaults.standard.synchronize()
}
} else {
print("Did not receive json")
}
//expectation.fulfill()
}
For the Authorisation Token, the ideal practice is from server side they need to check, requested API call have TOKEN is valid or not. And if the token is not matched or expired, they will provide HTTP status code 401, from Mobile side you need to check the status code first and if you found 401 you need to forcefully logout or re login which takes a new token and you can save it in your UserDefaults.
Scenario 1 : You need to tell to backend developer who made your webservice, that he need to check if TOKEN is valid or not. if token is expired he need to give message code or message that "Token has been expired" and you can check in Response if message code is for expired than you need to navigate your Login screen.
This is best practice.
Scenario 2 : If you dont want to Logout from app, and keep app going with new token automatically refresh, tell webservice developer that whenever token will be expired he will return new Token in response field as "Authorization" And from your code side, you need to check in each request whether Authorization contains new token.. if it contains that means you need to replace old token with New one in userdefault.
Below is my code in Swift3 :
func requestApiCall(_ urlString: String, paramData: NSObject, completionHandler: #escaping (NSDictionary?, NSError?) -> ()) {
let token = UserDefaults.standard.object(forKey: “token” as String)
var headersVal = [
"Authorization": "Bearer "+(token as String),
]
Alamofire.request(urlString, method: .post, parameters: paramData as? [String : AnyObject],encoding: JSONEncoding.default, headers: headersVal)
.responseJSON { response in
if let authorization = response.response?.allHeaderFields["Authorization"] as? String {
var newToken : String = authorization
UserDefaults.standard.set(newToken, forKey: "token")
UserDefaults.standard.synchronize()
}
switch response.result {
case .success(let value):
if let res = response.result.value {
let response = res as! NSDictionary
let message = response.object(forKey: "message")!
print(message)
if message as! String == "Token has been expired"
{
self.showLoginScreen()
}
}
completionHandler(value as? NSDictionary, nil)
case .failure(let error):
if error._code == -1001 {
print("timeout")
}
completionHandler(nil, nil)
}
}
}
Call service for new token when token expires is unsecure to your app because if token expires and you call service for new token then anyone can access your app or its data. The better way is to logout/sign out the user and ask him to login again.
but if you want to do so then make a method which is user to get new token and call it once you will get token expire code.
but you need to discuss with your backend developer for this new api and accordingly you can move farther
let me know for help . Thanks
I preferred to use Moya framework (abstraction under Alamofire) with PromiseKit. It makes easier write async code and create dependencies between parts. I wrapped all business logic for sending requests in the class. The main function of the request (public func register(device: String, type: String) -> Promise<Device>) returns Promise object. In the recover block I validate error code and if needed I refresh token and repeat failed request. Code example below:
public protocol HttpClientInterface {
...
func register(device: String, type: String) -> Promise<Device>
...
}
public func register(device: String, type: String) -> Promise<Device> {
return self.sendRegisterRequest(with: device, type: type)
.then {
self.parseRegister(response: $0)
}
.recover { lerror -> Promise<Device> in
guard let error = lerror as? HttpClientError,
case .server(let value) = error,
value == ServerError.notAuthenticated,
let refreshToken = self.credentialsStore.get()?.token?.refreshToken else {
throw lerror
}
return self.sendRefreshSessionRequest(operatorId: self.operatorId, refreshToken: refreshToken)
.then { data -> Promise<AuthToken> in
return self.parseRefreshSession(data)
}
.then { token -> Promise<Device> in
self.credentialsStore.update(token: token)
return self.register(device: device, type: type)
}
.catch { error in
if let myError = error as? HttpClientError,
case .server(let value) = httpError,
value == ServerError.notAuthenticated {
self.credentialsStore.accessDenied()
}
}
}
}
func sendRegisterRequest(with name: String, type: String) -> Promise<Data> {
return Promise { fulfill, reject in
self.client.request(.register(device: name, type: type)) {
self.obtain(result: $0,
successStatusCodes: [200, 201],
fulfill: fulfill,
reject: reject)
}
}
}
func parseRegister(response data: Data) -> Promise<Device> {
return Promise { fulfill, reject in
if let account = Account(data: data),
let device = account.devices?.last {
fulfill(device)
} else {
reject(HttpClientError.internalError())
}
}
}
I am using Alamofire to make REST calls to my server to get, add, update, and delete objects. What I'm wondering is, is it possible (and recommended) to wrap the Alamofire calls into my own custom objects (DTO), so that I can simply do things like user.delete(id) and user.add(newUser) instead of having Alamofire code all over my codebase? If so, how can I do it so I can return the success and failure handlers (the "promise" from the javascript world)? Something like this:
user.add(newUser)
.success(userObject){
}
.error(response){
}
Here is my current code:
//register a user
let parameters = [“username”: txtUsername.text! , "password": txtPassword.text!]
Alamofire.request(.POST, “http://myserver.com/users", parameters: parameters, encoding: .JSON)
.validate()
.responseObject { (response: Response<User, NSError>) in
if let user = response.result.value {
self.user = user
}
}
}
User.swift
final class User : ResponseObjectSerializable, ResponseCollectionSerializable {
var id: Int
var firstName: String?
var lastName: String?
var email: String?
var password: String?
init?(response: NSHTTPURLResponse, representation: AnyObject) {
id = representation.valueForKeyPath("id") as! Int
firstName = representation.valueForKeyPath("first_name") as? String
lastName = representation.valueForKeyPath("last_name") as? String
email = representation.valueForKeyPath("email") as? String
password = representation.valueForKeyPath("password") as? String
}
static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [User] {
var users: [User] = []
if let representation = representation as? [[String: AnyObject]] {
for userRepresentation in representation {
if let user = User(response: response, representation: userRepresentation) {
users.append(user)
}
}
}
return users
}
//Can I wrap my alamofire calls in methods like this:
func getById(id: Int)
func getAll()
func add(user: User)
func update (user: User)
func delete(id: int)
}
i think a static func is what you want , i wrote a example for it :
final class User{
var valueHandle :((AnyObject) -> ())?
var errorHandle :((NSError)->())?
func success(value:(AnyObject) -> ())->Self{
//pass success handle
self.valueHandle = value
return self
}
func error(error:(NSError)->())->Self{
//pass error handle
self.errorHandle = error
return self
}
static func getById(id: Int)->User{
return userRequest(.GET, urlString: "https://httpbin.org/get", param: ["foo": "bar"])
}
static func userRequest(method:Alamofire.Method , urlString:String ,param:[String: AnyObject]?) -> User {
let user = User()
Alamofire.request(method, urlString, parameters:param)
.validate()
.responseJSON { response in
switch response.result {
case .Success(let value):
//invoke with your back userobj
user.valueHandle?(value)
print(value)
case .Failure(let error):
user.errorHandle?(error)
}
}
return user
}
}
then you can use like :
User.getById(1)
.success { (value) in
print("value = \(value)")
}
.error { (error) in
print("error = \(error)")
}
hope it be helpful :D
I'm trying to pass data from my Data class to my Login class (both files are separate.), but it throws 2 errors from "Login" class as it appears unable to receive the data
Below are the errors shown in Login.swift :
Error 1
Instance member 'dataObj' cannot be used on type 'Login'
Error 2
Expected declaration
Login.swift code
import Foundation
import Alamofire
import SwiftyJSON
class Login {let dataObj = Data(userName: "username", passWord: "password")
let endPoint = dataObj.todoEndPoint
let parameters = [
"username": dataObj.userName,
"password": dataObj.passWord
]
Alamofire.request(.POST, endPoint, parameters:parameters)
.responseJSON { response in
print(response.request)
print(response.response)
print(response.result)
if let JSON = response.result.value {
print("Did receive JSON data: \(JSON)")
}
else {
print("JSON data is nil.")
}
}
}
Data.swift code
import Foundation
import Alamofire
import SwiftyJSON
class Data {
var userName:String!
var passWord:String!
let todoEndpoint: String = "http://jsonplaceholder.typicode.com/todos/1"
init(userName : String, passWord : String) {
self.userName = userName
self.passWord = passWord
}
}
Screenshot of errors
You just have to change the Data instance property to class property by using static
class Data {
static var userName:String!
static var passWord:String!
static let todoEndpoint: String = "http://jsonplaceholder.typicode.com/todos/1"
}
and set direct values in userName and passWord.
in from login set value
Data.userName = "yourusername"
Data.passWord = "password"
2.
or you can also create Data object
class Data {
var userName:String!
var passWord:String!
let todoEndpoint: String = "http://jsonplaceholder.typicode.com/todos/1"
init(userName : String, passWord : String) {
self.userName = userName
self.passWord = passWord
}
and in login class
class Login {
static let dataObj = Data(userName: "usename", passWord: "password")
lazy var endPoint = Login.dataObj.todoEndpoint
let parameters = [
"username": dataObj.userName,
"password": dataObj.passWord
]
func getRequest() {
Alamofire.request(.POST, endPoint, parameters:parameters)
.responseJSON { response in
print(response.request)
print(response.response)
print(response.result)
if let JSON = response.result.value {
print("Did receive JSON data: \(JSON)")
}
else {
print("JSON data is nil.")
}
}
}
}
UPDATED
I guess you should create a singleton object to get a values of users everywhere.
class Data {
static let sharedInstance = Data()
static let todoEndpoint: String = "http://jsonplaceholder.typicode.com/todos/1"
var userName: String?
var passWord: String?
// for prevent from creating this class object
private init() { }
}
And in Login class.
class Login {
init(userName: String, passWord: String) {
Data.sharedInstance.userName = userName
Data.sharedInstance.passWord = passWord
}
/// call this method to login
func getRequest() {
print(Data.sharedInstance.userName)
print(Data.sharedInstance.passWord)
Alamofire.request(.POST, Data.endPoint, parameters: ["userName": Data.sharedInstance.userName, "passWord": Data.sharedInstance.passWord])
.responseJSON { response in
print(response.request)
print(response.response)
print(response.result)
if let JSON = response.result.value {
print("Did receive JSON data: \(JSON)")
}
else {
print("JSON data is nil.")
}
}
}
}
And from some other class create a Login instance and pass userName and Password values from there and call getRequest method using Login object.
According to your Data class implementation you should use it like :
class Login {
let dataObj = Data(userName : "username", passWord : "password")
let endPoint = dataObj.todoEndPoint
let parameters = [
"username": dataObj.userName,
"password": dataObj.passWord
]
Alamofire.request(.POST, endPoint, parameters:parameters)
.responseJSON { response in
print(response.request)
print(response.response)
print(response.result)
if let JSON = response.result.value {
print("Did receive JSON data: \(JSON)")
}
else {
print("JSON data is nil.")
}
}
}
If you want to get static data from Data then use singleton/shared instance.
I use Swift 2 and Xcode 7.1.
I have a function who connect my users, but it will connect at my database with HTTP. I use Alamofire for execute this request. I want to know, from a view controller if the user is connected.
I have my function connect in a class. And i test connection in a ViewController.
Like this :
class user {
// ...
func connectUser(username: String, password: String){
let urlHost = "http://localhost:8888/project350705/web/app_dev.php/API/connect/"
let parametersSymfonyG = [
username, password
]
let url = UrlConstruct(urlHost: urlHost).setSymfonyParam(parametersSymfonyG).getUrl()
//var userArray = [String:AnyObject]()
Alamofire.request(.GET, url)
.responseString { response in
if let JSON = response.result.value {
var result = self.convertStringToDictionary(JSON)!
if result["status"] as! String == "success"{
let userArray = result["user"] as! [String:AnyObject]
userConnect = self.saveUser(userArray)
} else{
print("ERROR-CONNECTION :\n Status :\(result["status"]!)\n Code :\(result["code"]!)")
}
return ""
}
}
}
// ...
}
class MyViewController: UIViewController {
// ...
#IBAction func connect(sender: AnyObject?) {
// CONNECTION
User.connectUser(self.username.text!, password: self.password.text!)
// CHECK
if userConnect != nil {
print("connected")
}else{
print("NotConnected")
}
}
// ...
}
First solution : Return
To do so would require that my function returns a Boolean.
Only I can not use return.
Alamofire.request(.GET, url)
.responseString { response in
if let JSON = response.result.value {
var result = self.convertStringToDictionary(JSON)!
if result["status"] as! String == "success"{
let userArray = result["user"] as! [String:AnyObject]
userConnect = self.saveUser(userArray)
} else{
print("ERROR-CONNECTION :\n Status :\(result["status"]!)\n Code :\(result["code"]!)")
}
return "" // Unexpected non-void return value in void function
}
}
Second solution :
I can also test if the user has been logged, but before testing, I must wait for the function have finished loading.
users.connectUser(self.username.text!, password: self.password.text!)
// after
if userConnect != nil {
print("connected")
}else{
print("NotConnected")
}
I would prefer return a boolean. It will facilitate the processing.
Do you have a solution ?
I would suggest employing a completion handler in your connectUser method:
func connectUser(username: String, password: String, completion: #escaping (Bool) -> Void) {
// build the URL
// now perform request
Alamofire.request(url)
.responseString { response in
if let json = response.result.value, let result = self.convertStringToDictionary(json) {
completion(result["status"] as? String == "success")
} else {
completion(false)
}
}
}
You can then call it using:
users.connectUser(username.text!, password: password.text!) { success in
if success {
print("successful")
} else {
print("not successful")
}
}
// But don't use `success` here yet, because the above runs asynchronously
BTW, if your server is really generating JSON, you might use responseJSON rather than responseString, further streamlining the code and eliminating the need for convertStringToDictionary:
func connectUser(username: String, password: String, completion: #escaping (Bool) -> Void) {
// build the URL
// now perform request
Alamofire.request(url)
.responseJSON { response in
if let dictionary = response.result.value as? [String: Any], let status = dictionary["status"] as? String {
completion(status == "success")
} else {
completion(false)
}
}
}
If you've written your own server code to authenticate the user, just make sure you set the right header (because responseJSON not only does the JSON parsing for you, but as part of its validation process, it makes sure that the header specifies JSON body; it's good practice to set the header, regardless). For example in PHP, before you echo the JSON, set the header like so:
header("Content-Type: application/json");
The completion handler of your Alamofire.request method is asynchronous and it doesn't have a return type specified in its signature. Thats why you see an error when you provide a return statement in your completion handler closure.
You will have to split your request and response processing to separate methods and call the response processing method instead of using return statement.
Alamofire.request(.GET, url).responseString { response in
if let JSON = response.result.value {
var result = self.convertStringToDictionary(JSON)!
if result["status"] as! String == "success"{
let userArray = result["user"] as! [String:AnyObject]
userConnect = self.saveUser(userArray)
processSuccessResponse() //Pass any parameter if needed
} else{
print("ERROR-CONNECTION :\n Status :\(result["status"]!)\n Code :\(result["code"]!)")
processFailureResponse() //Pass any parameter if needed
}
}
}
func processSuccessResponse() {
//Process code for success
}
func processFailureResponse() {
//Process code for failure
}
My preferred way of doing this is to call a function in the completion handler. You can also set a boolean flag in order to check if the user is connected at any given time.
func connectUser(username: String, password: String, ref: MyClass) {
Alamofire.request(.GET, url)
.responseString { response in
var userIsConnected = false
if let JSON = response.result.value {
var result = self.convertStringToDictionary(JSON)!
if result["status"] as! String == "success"{
let userArray = result["user"] as! [String:AnyObject]
userConnect = self.saveUser(userArray)
userIsConnected = true
} else {
print("ERROR-CONNECTION :\n Status :\(result["status"]!)\n Code :\(result["code"]!)")
}
} else {
print("Response result nil")
}
ref.finishedConnecting(userIsConnected)
}
}
}
class MyClass {
var userIsConnected = false
func startConnecting() {
connectUser(username, password: password, ref: self)
}
func finishedConnecting(success: Bool) {
userIsConnected = success
... post-connection code here
}
}
I am developing an iPad application using Swift. For http requests I use Alamofire library. So far I have managed to pull some data from an API. But the problem is since it is an asynchronous call I don't know how to check whether the request has completed. Any help would be appreciated.
This is the code I have implemented so far
Client class
func getUsers(completionHandler: ([User]?, NSError?) -> ()) -> (){
var users: [User] = []
let parameters = [
"ID": "123",
"apikey": "1234",
]
Alamofire.request(.GET, "API_URL", parameters: parameters).responseJSON() {
(_, _, JSON, error) in
let items = (JSON!.valueForKey("Users") as! [NSDictionary])
for item in items {
var user: User = User()
user.userId = item.valueForKey("ID")! as? String
user.userName = item.valueForKey("Name")! as? String
user.group = item.valueForKey("Group")! as? String
users.append(user)
}
completionHandler(users, error)
}
}
Main class
func getUsers(){
FacilityClient().getUsers() { (users, error) -> Void in
if users != nil {
self.users = users!
} else{
println("error - \(error)")
}
}
tableUsers.reloadData()
}
Thank you.
The closure part of the Alamofire request is called, when the request has completed. I've used your code and commented the line where the request hast finished:
Alamofire.request(.GET, "API_URL", parameters: parameters).responseJSON() {
(_, _, JSON, error) in
//
// The request has finished here. Check if error != nil before doing anything else here.
//
let items = (JSON!.valueForKey("Users") as! [NSDictionary])
for item in items {
var user: User = User()
user.userId = item.valueForKey("ID")! as? String
user.userName = item.valueForKey("Name")! as? String
user.group = item.valueForKey("Group")! as? String
users.append(user)
}
//
// After creating the user array we will call the completionHandler, which probably reports back to a ViewController instance
//
completionHandler(users, error)
}
If you want the tableView to reload data after successfully fetching data over network, you will need to call tableUsers.reloadData() inside that completion handler, like this:
func getUsers(){
FacilityClient().getUsers() { (users, error) -> Void in
if users != nil {
self.users = users!
tableUsers.reloadData() // SHOULD be here
} else{
println("error - \(error)")
}
}
// tableUsers.reloadData() // WAS here
}
And Alamofire make sure the completion handler is called on main queue if you don't specify a queue. You don't need to add code to make it be called on mainThread again.
And to your originally question: The only way Alamofire let you know a request has completed is by calling that completion handler which you give to the Alamofire.request method.