0

I was trying to create a post method so I could reuse it further in my code.
I saw this example Returning data from async call in Swift function that gives partial solution to my problem but don't know how to call the function once I define it.

This is the function I am trying to call:

class func postRequest(url: URL, request: URLRequest, saveCookie: Bool, completionHandler: @escaping (_ postRequestStatus: [String:Any]) -> ()) {
        let session = URLSession.shared
        //So now no need of type conversion
        let task = session.dataTask(with: request) {
            (data, response, error) in
            func displayError(_ error: String) {
                print(error)
            }

            /* GUARD: Was there an error? */
            guard (error == nil) else {
                displayError("There was an error with your request: \(String(describing: error))")
                return
            }

            guard let statusCode = (response as? HTTPURLResponse)?.statusCode, statusCode >= 200 && statusCode <= 299 else {
                displayError("Your request returned a status code other than 2xx!")
                return
            }

            /* GUARD: Was there any data returned? */
            guard let data = data else {
                displayError("No data was returned by the request!")
                return
            }

            /* Since the incoming cookies will be stored in one of the header fields in the HTTP Response,parse through the header fields to find the cookie field and save the data */
            if saveCookie{
                let httpResponse: HTTPURLResponse = response as! HTTPURLResponse
                let cookies = HTTPCookie.cookies(withResponseHeaderFields: httpResponse.allHeaderFields as! [String : String], for: (response?.url!)!)
                HTTPCookieStorage.shared.setCookies(cookies as [AnyObject] as! [HTTPCookie], for: response?.url!, mainDocumentURL: nil)
            }

            let json: [String:Any]?
            do
            {
                json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any] ?? [:]
            }
            catch
            {
                displayError("Could not parse the data as JSON: '\(data)'")
                return
            }

            guard let server_response = json else
            {
                displayError("Could not parse the data as JSON: '\(data)'")
                return
            }

            if let userID = server_response["UserID"] as? Int64 {
                print(userID)
                completionHandler(server_response)
            }else{
                displayError("Username or password incorrect.")
            }
        }
        return task.resume()
    }

This is the caller function:

class func loginPostRequest(post_data: [String:Any], completionHandler: @escaping (_ postRequestStatus: [String:Any]) -> ()){

    let url = URL(string: HTTPConstant.Login.Url)!
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    var paramString = ""
    for (key, value) in post_data
    {
        paramString = paramString + (key) + "=" + (value as! String) + "&"
    }
    request.httpBody = paramString.data(using: .utf8)
    //in the line below I get the error message, extra argument "request" in call.  
postRequest(url: url, request: request, saveCookie: true, completionHandler: { postRequestStatus in
        completionHandler(postRequestStatus)
    })
}
Community
  • 1
  • 1
Ra's al Ghul
  • 71
  • 10

2 Answers2

1

You cannot make loginPostRequest return NSDictionary because you are making async call with what you need is to create completion block same way you have create with postRequest method also from Swift 3 you need to use URLRequest with mutable var object instead of NSMutableURLRequest you need to also change the postRequest function's request argument type to URLRequest so latter no need to convert NSMutableURLRequest to URLRequest and use Swift type dictionary instead of NSDictionary

class func loginPostRequest(post_data: [String:Any], completionHandler: @escaping (_ postRequestStatus: [String:Any]) -> ()){

    let url = URL(string: HTTPConstant.Login.Url)!
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    var paramString = ""
    for (key, value) in post_data
    {
        paramString = paramString + (key as! String) + "=" + (value as! String) + "&"
    }
    request.httpBody = paramString.data(using: .utf8)
    postRequest(url: url, request: request, saveCookie: true, completionHandler: { postRequestStatus in
         completionHandler(postRequestStatus)
    })
}

Now simply changed the argument type of request to URLRequest from NSMutableURLRequest in method postRequest

class func postRequest(url: URL, request: URLRequest, saveCookie: Bool, completionHandler: @escaping (_ postRequestStatus: [String:Any]) -> ()) {
    let session = URLSession.shared
    //So now no need of type conversion
    let task = session.dataTask(with: request) { (data, response, error) in
       func displayError(_ error: String) {
            print(error)
        }

        /* GUARD: Was there an error? */
        guard (error == nil) else {
            displayError("There was an error with your request: \(String(describing: error))")
            return
        }

        guard let statusCode = (response as? HTTPURLResponse)?.statusCode, statusCode >= 200 && statusCode <= 299 else {
            displayError("Your request returned a status code other than 2xx!")
            return
        }

        /* GUARD: Was there any data returned? */
        guard let data = data else {
            displayError("No data was returned by the request!")
            return
        }

        /* Since the incoming cookies will be stored in one of the header fields in the HTTP Response,parse through the header fields to find the cookie field and save the data */
        if saveCookie{
            let httpResponse: HTTPURLResponse = response as! HTTPURLResponse
            let cookies = HTTPCookie.cookies(withResponseHeaderFields: httpResponse.allHeaderFields as! [String : String], for: (response?.url!)!)
            HTTPCookieStorage.shared.setCookies(cookies as [AnyObject] as! [HTTPCookie], for: response?.url!, mainDocumentURL: nil)
        }

        let json: [String:Any]?
        do
        {
            json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any] ?? [:]
        }
        catch
        {
            displayError("Could not parse the data as JSON: '\(data)'")
            return
        }

        guard let server_response = json else
        {
            displayError("Could not parse the data as JSON: '\(data)'")
            return
        }

        if let userID = server_response["UserID"] as? Int64 {
            print(userID)
            completionHandler(server_response)
        }else{
            displayError("Username or password incorrect.")
        }
    }
    return task.resume()
}

Now when you call this loginPostRequest you are having response in completion block of it.

Nirav D
  • 71,513
  • 12
  • 161
  • 183
  • I followed this approach, but I am getting an error in line: postRequest(url: url, request: yourUrlRequest, saveCookie: true, completionHandler: { postRequestStatus in completionHandler(postRequestStatus) }). Error message: Extra argument "request' in call. – Ra's al Ghul May 16 '17 at 06:08
  • I updated both fnction and error is in"loginPostRequest" function. – Ra's al Ghul May 16 '17 at 06:14
  • @Ra'salGhul Can you show the new declaration of method `postRequest` that you have changed from my solution? – Nirav D May 16 '17 at 06:21
  • Attached above is the new declaration of method postRequest. – Ra's al Ghul May 16 '17 at 06:23
  • @Ra'salGhul As of your `loginPostRequest` method is class method you need to also make `postRequest` as `class` so either add prefix class in both method or remove it from `loginPostRequest` and you all set to go also check carefully that I have replace all NSDictionary with `[String:Any]` you need to also do the same – Nirav D May 16 '17 at 06:30
  • I added (data, response, error) in this line: let task = session.dataTask(with: request) { (data, response, error) in As you had removed (data, response, error) and it was throwing an error. – Ra's al Ghul May 16 '17 at 06:30
  • That was a mistake. I Corrected it. New to stack overflow. Really appreciate your help. Can you help me with this one too? http://stackoverflow.com/questions/43993129/returning-data-from-async-call-that-takes-multiple-params-in-swift-function/43993365?noredirect=1#comment75058529_43993365 – Ra's al Ghul May 17 '17 at 04:32
  • @Ra'salGhul Let me check that mate :), If I have solution I will post it – Nirav D May 17 '17 at 04:36
  • Here it is/ Sorry for the typo. Thankful for your help. http://stackoverflow.com/questions/44015515/how-to-create-a-function-that-returns-the-return-value-from-async-call – Ra's al Ghul May 17 '17 at 04:45
  • @Ra'salGhul Let me check that mate :) – Nirav D May 17 '17 at 05:07
1

Functions that receive a closure as parameter can be called like any other functions:

postRequest(url: yourUrlObject, request: yourUrlRequest, saveCookie: true/false, completionHandler: { postRequestStatus in
    // ... code that will run once the request is done
})

If the closure is the last parameter you can pass it outside the parenthesis:

postRequest(url: yourUrlObject, request: yourUrlRequest, saveCookie: true/false) { postRequestStatus in
    // ... code that will run once the request is done
})

You can check the Swift book to learn more about closures and functions.

By the way, your postRequest method looks weird, I haven't checked deeply into it, but for instance I believe although url is one of the parameters it isn't actually used. Some other answer pointed other problems into that function.

Marco Pompei
  • 1,080
  • 1
  • 8
  • 18
  • I realized that I don't need to pass url as a param, thank you for pointing that out. But I followed this approach, but I am getting an error in line: postRequest(url: url, request: yourUrlRequest, saveCookie: true, completionHandler: { postRequestStatus in completionHandler(postRequestStatus) }). Error message: Extra argument "request' in call – Ra's al Ghul May 16 '17 at 06:10
  • And your `postRequest` function didn't changed? I don't get the same error here. – Marco Pompei May 16 '17 at 06:18
  • Attached above is the new declaration of method postRequest. I just changed NSDictionary to [String:Any] – Ra's al Ghul May 16 '17 at 06:25
  • I did a Playground with your updated code, I still don't get any errors. – Marco Pompei May 16 '17 at 06:49