How to check Alamofire request has completed - ios

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.

Related

How to set a class variable with value from Alamofire call in swift?

I am new to iOS development, and I have a class that I am using to retrieve data from a server. I would like to store a value from an Alamofire request as class variable for use with subsequent requests. I understand that Alamofire requests have completion handlers with them, but it is still not very clear to me how these work.
In setSessionID, I would like to store the String from generateNewSessionID into sessionID. This obviously isn't the correct way to do this, as I get an unwrapped nil optional error in the print statement.
import Alamofire
import Foundation
class DataRetriever {
private var sessionID: String?
private let baseUrl: String?
private let endPoints = ["session": "/session", "population": "/population/"]
init() {
let dict = NSDictionary(contentsOfFile: NSBundle.mainBundle().pathForResource("Config", ofType: "plist")!)
baseUrl = dict!["baseUrl"] as? String
setSessionID()
}
private func generateNewSessionID(completionHandler: (NSError?, String?) -> ()) {
let params = [ "stuff": "stuff", "morestuff": "moreStuff" ]
Alamofire.request(.POST, baseUrl! + endPoints["session"]!, parameters: params, encoding:ParameterEncoding.URL, headers: getSessionHeaders()).responseJSON { response in
switch response.result{
case .Success:
completionHandler(nil, String(response.response!))
case .Failure(let error):
completionHandler(error, nil)
}
}
}
func setSessionID() {
generateNewSessionID() { (error, session) in
if error != nil {
// handle error
} else {
self.sessionID = session
}
}
print(self.sessionID!)
}
}
I've looked through other examples, but I can't see how I would be able to do this within the scope of my class.
In your setSessionID if you check if error != nil that's mean if theres an error handle it, so you should change it like this
if error == nil {
// No error
self.sessionID = session
} else {
//handle Error
}
You're actually printing the sessionID outside of your completion block, which means it's trying to print the value before the network request has completed. You need to move it inside your completion block like so:
func setSessionID() {
generateNewSessionID() { (error, session) in
if error != nil {
// handle error
} else {
self.sessionID = session
}
// moved print here
print(self.sessionID!)
}
}
You should, of course, guard against the possibility that session could be nil:
guard let id = self.sessionID else {
return
}
print(id)
But assuming it's not, the first code block should work.

Populate UItableview with parse and url

What is the best practice for populating a tableview from a URL that takes user data from parse in the URL string. Currently the code is working, but it could result in an error if the parse user data isn't loaded before the URL request is run. This will cause the table to not load (not safe code)
The user class in parse has a key of "teamNumber" which plugs in to the URL to fetch a string of the page source. From there the string is manipulated to create an array that is displayed as the table view.
Here is the code (edited to take out the string manipulation):
override func viewDidLoad() {
super.viewDidLoad()
//To the internet to grab the "leagueNumber" of a user to input into the attemptedUrl string
let leagueNumber = PFUser.currentUser()!["leagueNumber"] as? String
//Plug in the league number from parse into URL string
let attemptedUrl = NSURL(string: "http://someURL.leagueapps.com/leagues/\(leagueNumber!)/teams")
if let url = attemptedUrl {
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) -> Void in
if let urlContent = data {
let webContent = NSString(data: urlContent, encoding: NSUTF8StringEncoding)
//Manipulate string to get the team information
//Ends up with array [team1, team2, team3, team4] to populate the tableview
}
} else {
print("URL could not connect")
}
//Close internet session
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
}
task.resume()
} else {
print("Fail")
}
}
Is there a safer way to implement this code?
I supposed you could make it "safer" by fetching the full user object from parse with a query, and then forming your URL with that result:
var query = PFUser.query
var currentUser = PFUser.currentUser()
query.whereKey("username", equalTo: currentUser.username)
query.getFirstObjectInBackgroundWithBlock {
(object: PFObject?, error: NSError?) -> Void in
if error != nil || object == nil {
println("The getFirstObject request failed.")
} else {
// The find succeeded.
leagueNumber = object["leagueNumber"] as! String!
println("Successfully retrieved the object.")
}
}

Return Bool in Alamofire closure

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
}
}

Need to return json obj so I display in list view getting back nil

I am very new to swift. I am trying to return back the JSON and view it in a list view, I cant get the JSON from my AppApi class to return back to my viewDidLoad(). Any help would be appreciated.
Thank you in advanced.
Teli
override func viewDidLoad() {
super.viewDidLoad()
let api = AppAPI(token:self.toPassToken)
var test = api.getOrders()
println("why does test come back as an empty array")
println(test)
println(test.count)
}
class AppAPI {
var token: String
let apiEndPoint = "endpoint"
let apiUrl:String!
let consumerKey:String!
let consumerSecret:String!
var returnData = [:]
init(token:String){
self.apiUrl = “hidden-for-security”
self.consumerKey = "token"
self.consumerSecret = "my consumer secret"
self.token = token
}
func getOrders() -> [JSON] {
return makeCall("contacts")
}
func makeCall(section:String) -> [JSON] {
let params = ["token":"\(self.token)"]
Alamofire.request(.POST, "\(self.apiUrl)", parameters: params)
.responseJSON { (request, response, json, error) -> Void in
println("error \(request)")
self.returnData = json! as! NSDictionary
}
return results!
}
}
In your makeCall(section:String) -> [JSON] function you are returning results!. Where is results ever set in this function?
Did you mean to return returnData instead?
Alamofire.request performs an asynchronous request; responseJSON will execute that closure when the request completes at some point in the future.
You almost certainly do not want to block your viewDidLoad method until this request finishes as that would block your main thread and leave the app unresponsive. Instead start the request in viewDidLoad and react whenever the request finishes.
One way you might do this is by passing a closure to getOrders which you could execute when the request finishes.

How can I grab the correct JSON response using SwiftyJSON?

I am making a GET request using alamofire and attempting to convert to JSON using SwiftyJSON. I can successfully make the request, but I'm unable to convert the response into a usable JSON object.
I am trying to grab data from a foursquare venue detail response, and update a UITableView (the detail page) with appropriate information about the venue. The UITableView has 3 different sections.
Here is my makeRequest code for VenueDetailViewController (UITableViewController).
func makeRequest() {
Alamofire.request(.GET, self.foursquareEndpointURL, parameters: [
"client_id" : self.foursquareClientID,
"client_secret" : self.foursquareClientSecret,
"v" : "20140806"
])
.responseJSON { (request, response, data, error) in
println(request)
println(response)
println(error)
println(data)
if data != nil {
var jsonObj = JSON(data!)
// response is not in the form of an array? I think... thats why data is not setting .. I think "arrayValue" below is incorrect. should be something like dictionaryValue or stringValue
if let obj = jsonObj["response"]["venue"].arrayValue as [JSON]? {
self.responseitems = obj
self.tableView.reloadData()
println("Objis: \(obj)")
}
}
}
}
When I println("Obis: \(obj)") I get a console output of Objis: [] telling me that I'm grabbing the wrong data from the JSON response? Here is the url for the Foursquare Venue Detail API request
Oh and self.responseitems is var responseitems:[JSON] = []
Please help! Thanks
Check out this code:
let url = "Foursquare Venue Detail API request goes here"
Alamofire .request(.GET, url) .responseJSON(options: nil) { (_, _, data, error) -> Void in
if error != nil {
println(error?.localizedDescription)
} else if let data: AnyObject = data {
let jObj = JSON(data)
if let venue = jObj["response"]["venue"].dictionary {
println(venue)
}
}
}
So the main difference here is that the data is a dict, not an array.
By the way, consider reloading the table view in the main queue as this affects the UI:
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData() // Update UI
}

Resources