By u54r


2014-12-09 23:15:38 8 Comments

I am making url calls thru an API that I created using swift as follows:

class API {

  let apiEndPoint = "endpoint"
  let apiUrl:String!
  let consumerKey:String!
  let consumerSecret:String!

  var returnData = [:]

  init(){
    self.apiUrl = "https://myurl.com/"
    self.consumerKey = "my consumer key"
    self.consumerSecret = "my consumer secret"
  }

  func getOrders() -> NSDictionary{
    return makeCall("orders")
  }

  func makeCall(section:String) -> NSDictionary{

    let params = ["consumer_key":"key", "consumer_secret":"secret"]

    Alamofire.request(.GET, "\(self.apiUrl)/\(self.apiEndPoint + section)", parameters: params)
        .authenticate(user: self.consumerKey, password: self.consumerSecret)
        .responseJSON { (request, response, data, error) -> Void in
            println("error \(request)")
            self.returnData = data! as NSDictionary
    }
    return self.returnData
  }

}

I call this API in my UITableViewController to populate the table with SwiftyJSON library. However my returnData from the API is always empty. There is no problem with Alomofire calls as I can successfully retrieve value. My problem is how I am supposed to carry this data over to my table view controller?

var api = API()
api.getOrders()
println(api.returnData) // returnData is empty

5 comments

@Rob 2014-12-10 04:05:45

As mattt points out, Alamofire is returning data asynchronously via a “completion handler” pattern, so you must do the same. You cannot just return the value immediately, but you instead want to change your method to not return anything, but instead use a completion handler closure pattern.

Nowadays, that might look like:

func getOrders(completionHandler: @escaping (Result<[String: Any]>) -> Void) {
    performRequest("orders", completion: completionHandler)
}

func performRequest(_ section: String, completion: @escaping (Result<[String: Any]>) -> Void) {
    let url = baseURL.appendingPathComponent(section)
    let params = ["consumer_key": "key", "consumer_secret": "secret"]

    Alamofire.request(url, parameters: params)
        .authenticate(user: consumerKey, password: consumerSecret)
        .responseJSON { response in
            switch response.result {
            case .success(let value as [String: Any]):
                completion(.success(value))

            case .failure(let error):
                completion(.failure(error))

            default:
                fatalError("received non-dictionary JSON response")
            }
    }
}

Then, when you want to call it, you use this completion closure parameter (in trailing closure, if you want):

api.getOrders { result in
    switch result {
    case .failure(let error):
        print(error)

    case .success(let value):
        // use `value` here
    }
}

// but don't try to use the `error` or `value`, as the above closure
// has not yet been called
//

@zerohedge 2015-12-21 16:43:18

Hi @Rob, I have used your code almost one-for-one to make an asynchronous request. My goal as a start is to get a [String] and use that variable.count to determine how many numberOfRowsInSection in my tableView. Your example really helped me, but there's nothing in it that actually "returns" something. When I do ConnectionsHelper.getOrders() in my own code, I can print details about my responseObject successfully, but not in any way return them or assign them to a variable in my ViewController file. Can you give a few more pointers? Thanks!

@Rob 2015-12-21 16:58:40

Correct, this doesn't "return" anything. It can't because it runs asynchronously. Call getOrders in viewDidLoad, for example and in the getOrders completion block (where I print responseObject), you will (a) update your property with the results of the asynchronous call; and (b) call [self.tableView reloadData] from that completion closure. So, your UITableViewDataSource methods will end up being called twice, once when you first load it (at which point no data is yet available); and again after you call reloadData.

@zerohedge 2015-12-21 21:26:38

Worked wonderfully, thank you! Do I have to write a separate completion handler for each function I have? (I will have over 10 total in my application)

@Rob 2015-12-21 22:59:05

Yes, wherever you call asynchronous method, yes, you'd generally employ this pattern.

@Mitesh Jain 2016-01-11 15:53:29

Your code looking good but give me this error. Command failed due to signal: Segmentation fault: 11 why so ?

@Jesus Rodriguez 2016-01-11 23:41:12

@Rob I am currently experiencing the same proble as Mitesh. How can you use breakpoints to Debug segmentation fault when the app is not running (this causes build fail)

@Rob 2016-01-12 00:42:52

@Granola - Ah, I misunderstood. Xcode gave you that segmentation fault, not your app. It looks like the updated Alamofire syntax causes problems for the compiler. You can use switch statement to check for .Success and .Failure independently, and that gets around that problem. It's not as compact, but is even more clear about the handling of the Alamofire Result enum.

@Rob 2016-01-12 00:44:05

@Mitesh - See revised example above. It looks like change in Alamofire was causing a crash in Xcode. I've revised my answer to get around this Xcode bug.

@Jesus Rodriguez 2016-01-12 02:45:17

@Rob I have tested the corrected code and works flawlessly now.

@Mitesh Jain 2016-01-12 06:03:50

@Rob I also have test updated code and it's working fine. Many thanks for your quick update

@kishorer747 2016-05-28 07:48:12

perfect! thanks for this. My code is now lot cleaner and correct. For all async calls, this should be the way. Just a change if you want to parse the response as a json: instead of completionHandler(value as? NSDictionary, nil) just do completionHandler(value, nil) and replace NSDictionary in getOrders and makeCall with AnyObject. value is the responseObject as it is. You can now parse this value in the handler like this: let json = JSON(responseObject!)

@Jenita _Alice4Real 2016-10-12 10:36:27

@kishorer747 : where do i use the json = JSON(responseObject!) exactly?? Sorry i'm new to this.Thanx!

@Rob 2016-10-12 14:57:22

@Jenita_Alice4Real - I'm not a fan of SwiftyJSON so I wouldn't add that line at all. But, if you were to use it, you'd often do that right before you call the closure, and you'd change that parameter if the closure from a dictionary to a JSON.

@kishorer747 2016-10-13 05:10:09

@Jenita_Alice4Real here is the answer to use SwiftyJSON to parse a json: stackoverflow.com/a/40012727/2177085

@Tunds 2016-12-28 19:44:41

Can i ask why does this answer use 2 completion handlers rather than one? Since this would still work just by calling the makeCall function. Just curious

@Rob 2016-12-28 19:57:47

You should ask the OP, since that part was taken from his question. I'm assuming that there were details in getOrders that were omitted for the sake of brevity, not relevant to the immediate question. It's not unusual, though, to have one method doing low network stuff (building some standard POST request, handling activity indicators, etc.) and another handling application-level stuff (e.g. choosing endpoint, serializing JSON into objects, etc.). As it is, getOrders is offering little additional value, but I suspect in his real app, it was doing some more meaningful stuff there.

@Vasily Bodnarchuk 2017-12-01 14:19:33

Details

xCode 9.1, Swift 4

Features:

  • Easy readable code
  • Ready templates (it's easy to add more requests)
  • Embedded solution with asynchronous data processing
  • Full examples

Sample 1

Return data using closure

Data1.searchRequest(term: "jack johnson") { json, error  in
     print(error ?? "nil")
     print(json ?? "nil")
     print("Update views")
}

Full sample 1

Data class

import Alamofire

class Data1 {

    static fileprivate let queue = DispatchQueue(label: "requests.queue", qos: .utility)
    static fileprivate let mainQueue = DispatchQueue.main

    fileprivate class func make(request: DataRequest, closure: @escaping (_ json: [String: Any]?, _ error: Error?)->()) {
        request.responseJSON(queue: Data1.queue) { response in

            // print(response.request ?? "nil")  // original URL request
            // print(response.response ?? "nil") // HTTP URL response
            // print(response.data ?? "nil")     // server data
            //print(response.result ?? "nil")   // result of response serialization

            switch response.result {
            case .failure(let error):
                Data1.mainQueue.async {
                    closure(nil, error)
                }

            case .success(let data):
                Data1.mainQueue.async {
                    closure((data as? [String: Any]) ?? [:], nil)
                }
            }
        }
    }

    class func searchRequest(term: String, closure: @escaping (_ json: [String: Any]?, _ error: Error?)->()) {
        let request = Alamofire.request("https://itunes.apple.com/search?term=\(term.replacingOccurrences(of: " ", with: "+"))")
        Data1.make(request: request) { json, error in
            closure(json, error)
        }
    }
}

UIViewController

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        Data1.searchRequest(term: "jack johnson") { json, error  in
            print(error ?? "nil")
            print(json ?? "nil")
            print("Update views")
        }
    }
}

Sample 2

Return data using delegate

// ....
var data = Data2()
data.delegate = self
data.searchRequest(term: "jack johnson")
// ....

extension ViewController: Data2Delegate {
    func searchRequest(response json: [String : Any]?, error: Error?) {
        print(error ?? "nil")
        print(json ?? "nil")
        print("Update views")
    }
}

Full sample 2

Data class

import Alamofire

protocol Data2Delegate: class {
    func searchRequest(response json: [String: Any]?, error: Error?)
}

class Data2 {

    fileprivate let queue = DispatchQueue(label: "requests.queue", qos: .utility)
    fileprivate let mainQueue = DispatchQueue.main

    weak var delegate: Data2Delegate?

    fileprivate func make(request: DataRequest, closure: @escaping (_ json: [String: Any]?, _ error: Error?)->()) {
        request.responseJSON(queue: queue) { response in

            // print(response.request ?? "nil")  // original URL request
            // print(response.response ?? "nil") // HTTP URL response
            // print(response.data ?? "nil")     // server data
            //print(response.result ?? "nil")   // result of response serialization

            switch response.result {
            case .failure(let error):
                self.mainQueue.async {
                    closure(nil, error)
                }

            case .success(let data):
                self.mainQueue.async {
                    closure((data as? [String: Any]) ?? [:], nil)
                }
            }
        }
    }

    func searchRequest(term: String) {
        let request = Alamofire.request("https://itunes.apple.com/search?term=\(term.replacingOccurrences(of: " ", with: "+"))")
        make(request: request) { json, error in
            self.delegate?.searchRequest(response: json, error: error)
        }
    }
}

UIViewController

import UIKit

class ViewController: UIViewController {
    private var data = Data2()
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        data.delegate = self
        data.searchRequest(term: "jack johnson")
    }
}

extension ViewController: Data2Delegate {
    func searchRequest(response json: [String : Any]?, error: Error?) {
        print(error ?? "nil")
        print(json ?? "nil")
        print("Update views")
    }
}

Sample 3

Return data using PromiseKit

_ = data.searchRequest(term: "jack johnson").then { response in
      print(response.error ?? "nil")
      print(response.json ?? "nil")
      print("Update views")
      return .void
}

Full sample 3

Data class import Alamofire import PromiseKit

class Data3 {

    fileprivate let queue = DispatchQueue(label: "requests.queue", qos: .utility)
    fileprivate let mainQueue = DispatchQueue.main

    fileprivate func make(request: DataRequest) -> Promise<(json:[String: Any]?, error: Error?)> {
         return Promise { fulfill, reject in
            request.responseJSON(queue: queue) { response in

                // print(response.request ?? "nil")  // original URL request
                // print(response.response ?? "nil") // HTTP URL response
                // print(response.data ?? "nil")     // server data
                //print(response.result ?? "nil")   // result of response serialization

                switch response.result {
                    case .failure(let error):
                        self.mainQueue.async {
                            fulfill((nil, error))
                        }

                    case .success(let data):
                        self.mainQueue.async {
                            fulfill(((data as? [String: Any]) ?? [:], nil))
                        }
                }
            }
        }
    }

    func searchRequest(term: String) -> Promise<(json:[String: Any]?, error: Error?)> {
        let request = Alamofire.request("https://itunes.apple.com/search?term=\(term.replacingOccurrences(of: " ", with: "+"))")
        return make(request: request)
    }
}

extension AnyPromise {

    class var void: AnyPromise {
        return AnyPromise(Promise<Void>())
    }
}

UIViewController

import UIKit
import PromiseKit

class ViewController: UIViewController {
    private var data = Data3()
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        _ = data.searchRequest(term: "jack johnson").then { response in
            print(response.error ?? "nil")
            print(response.json ?? "nil")
            print("Update views")
            return .void
        }
    }
}

@MAS. John 2017-12-21 06:25:09

i like sample 1 but i confuse .. how to insert headers, body and method ?

@Vasily Bodnarchuk 2017-12-21 06:32:30

All examples are complete and working. Create a new project and experiment with the code. Add new queries to the date class. And usage code placed in UIViewController class

@MAS. John 2017-12-21 07:17:00

alright .. i found the solution .. thanks ..

@kishorer747 2016-10-13 05:09:20

To parse a json using Swifty JSON, here is how i am doing it.

For @Jenita _Alice4Real

func uploadScans(parameters: [String: AnyObject], completionHandler: (AnyObject?, NSError?) -> ()) {
    makePostCall(CommonFunctions().getSaveSKUDataUrl(), parameters: parameters,completionHandler: completionHandler)
}

func makePostCall(url: String, parameters: [String: AnyObject], completionHandler: (AnyObject?, NSError?) -> ()) {
    Alamofire.request(.POST, url, parameters: parameters)
        .responseJSON { response in
            switch response.result {
                case .Success(let value):
                    completionHandler(value, nil)
                case .Failure(let error):
                    completionHandler(nil, error)
            }
    }
}

uploadScans(params) { responseObject, error in
    let json = JSON(responseObject!)
}

@n.by.n 2016-04-08 14:20:49

Following is the complete flow for performing the 'Login Action' using Alamofire and Swift.

Alamofire v3.3 Swift 2.2 Xcode 7.3

I have used GCD and MBProgressHUD for my own convenience. Refactor and use as you like :)

func loginBtnTapped(sender: AnyObject) {

    MBProgressHUD.showHUDAddedTo(self.view, animated: true)

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {

        let loginInfo : Dictionary<String,AnyObject> = ["email":"[email protected]","password":"abc123"]

        self.loginUser(loginInfo) { responseObject, error in

            print("\(responseObject) \n  \(error) ")

            // Parsing JSON Below
            let status = Int(responseObject?.objectForKey("status") as! String)
            if status == 1 {
                // Login Successfull...Move To New VC
            }
            else {
                print(responseObject?.objectForKey("message"))! as! String)
            }
            return
        }
        dispatch_async(dispatch_get_main_queue()) {
            MBProgressHUD.hideHUDForView(self.view, animated: true)
        }
    }

}


func loginUser(parameters:NSDictionary, completionHandler: (NSDictionary?, NSError?) -> ()) {

    self.postRequest("http://qa.company.com/project/index.php/user/login",
                     paramDict: parameters as? Dictionary<String, AnyObject>,
                     completionHandler: completionHandler)
}

func postRequest(urlString: String, paramDict:Dictionary<String, AnyObject>? = nil,
                 completionHandler: (NSDictionary?, NSError?) -> ()) {

    Alamofire.request(.POST, urlString, parameters: paramDict)
        .responseJSON { response in
            switch response.result {
            case .Success(let JSON):
                completionHandler(JSON as? NSDictionary, nil)
            case .Failure(let error):
                completionHandler(nil, error)
            }
    }

}

@mattt 2014-12-09 23:41:19

From the Alamofire README (emphasis added):

Networking in Alamofire is done asynchronously. Asynchronous programming may be a source of frustration to programmers unfamiliar with the concept, but there are very good reasons for doing it this way.

Rather than blocking execution to wait for a response from the server, a callback is specified to handle the response once it's received. The result of a request is only available inside the scope of a response handler. Any execution contingent on the response or data received from the server must be done within a handler.

Related Questions

Sponsored Content

92 Answered Questions

17 Answered Questions

[SOLVED] How do I call Objective-C code from Swift?

  • 2014-06-02 20:05:42
  • David Mulder
  • 269785 View
  • 927 Score
  • 17 Answer
  • Tags:   objective-c swift

42 Answered Questions

[SOLVED] How to check for an active Internet connection on iOS or macOS?

34 Answered Questions

[SOLVED] How to change the name of an iOS app?

  • 2008-10-27 03:07:03
  • Robert Gould
  • 447878 View
  • 957 Score
  • 34 Answer
  • Tags:   ios

10 Answered Questions

[SOLVED] How to parse JSON response from Alamofire API in Swift?

2 Answered Questions

How to show images from API in CollectionView

2 Answered Questions

1 Answered Questions

[SOLVED] TableView not displaying text with JSON data from API call

1 Answered Questions

[SOLVED] Can't pull value from json data

2 Answered Questions

[SOLVED] JSON parsed data not being called? Swift

  • 2015-09-06 06:39:01
  • Syed Ariff
  • 308 View
  • 2 Score
  • 2 Answer
  • Tags:   ios json swift

Sponsored Content