Closure?

Hi all:

I am working on a POC IOS app that needs to authtenticate to a SA+QL Server Azure database. I wrote a web service in .NET, also osted on Azure, and found some examples that let me write the code below in my IOS app. So when the user taps the login button, the function calls the submitPost code, and the line print("response: ", utf8Representation) prints the response to the console. The web service will return "Success" if the authentication creentials are valid, and I need to return this value back to the login button function so I can take action based on the result, but I've been struggling trying to figure out how. Can anyone offer a suggestion? Note this is just a POC so if this isn't the best way to do this I am not worried about it right now, just need this to work! Thanks for any and all input!


Code in Login Function>>>

let myPost = User(username: "User1", password: "PW")

submitPost(post: myPost) { (error) in

if let error = error {

fatalError(error.localizedDescription)

}

}


Submit function>>>

func submitPost(post: User, completion:((Error?) -> Void)?)

{


var urlComponents = URLComponents()

urlComponents.scheme = "https"

urlComponents.host = "mysite1.azurewebsites.net"

urlComponents.path = "/Authenticate"

guard let url = urlComponents.url else { fatalError("Could not create URL from components") }

var request = URLRequest(url: url)

request.httpMethod = "POST"

var headers = request.allHTTPHeaderFields ?? [:]

headers["Content-Type"] = "application/json"

request.allHTTPHeaderFields = headers

let encoder = JSONEncoder()

do {

let jsonData = try encoder.encode(post)

request.httpBody = jsonData

print("jsonData: ", String(data: request.httpBody!, encoding: .utf8) ?? "no body data")

} catch {

completion?(error)

}

let config = URLSessionConfiguration.default

let session = URLSession(configuration: config)

let task = session.dataTask(with: request) { (responseData, response, responseError) in

guard responseError == nil else {

completion?(responseError!)

return

}

if let data = responseData, let utf8Representation = String(data: data, encoding: .utf8)

{

print("response: ", utf8Representation)

} else {

print("no readable data received in response")

}

}

task.resume()

}

Answered by OOPer in 397647022

Currently, one of the most popular ways is using `Result` type as an argument to the completion handler:

    func submitPost(post: User, completion:((Result<String, Error>) -> Void)?) {
        
        //...

        let encoder = JSONEncoder()
        do {
            let jsonData = try encoder.encode(post)
            request.httpBody = jsonData
            print("jsonData: ", String(data: request.httpBody!, encoding: .utf8) ?? "no body data")
        } catch {
            completion?(.failure(error)) //<-
        }
        
        let config = URLSessionConfiguration.default
        let session = URLSession(configuration: config)
        let task = session.dataTask(with: request) { (responseData, response, responseError) in
            guard responseError == nil else {
                completion?(.failure(responseError!)) //<-
                return
            }
            
            if
                let data = responseData,
                let utf8Representation = String(data: data, encoding: .utf8)
            {
                print("response: ", utf8Representation)
                completion?(.success(utf8Representation)) //<-
            } else {
                print("no readable data received in response")
                completion?(.success("no readable data received in response")) //or you may want to pass .failure with your own error
            }
        }
        task.resume()
    }

You can use it like this:

        submitPost(post: myPost) { (result) in
            switch result {
            case .success(let value):
                print(value)
                //...use value here
            case .failure(let error):
                print(error)
                fatalError(error.localizedDescription)
            }
        }
Accepted Answer

Currently, one of the most popular ways is using `Result` type as an argument to the completion handler:

    func submitPost(post: User, completion:((Result<String, Error>) -> Void)?) {
        
        //...

        let encoder = JSONEncoder()
        do {
            let jsonData = try encoder.encode(post)
            request.httpBody = jsonData
            print("jsonData: ", String(data: request.httpBody!, encoding: .utf8) ?? "no body data")
        } catch {
            completion?(.failure(error)) //<-
        }
        
        let config = URLSessionConfiguration.default
        let session = URLSession(configuration: config)
        let task = session.dataTask(with: request) { (responseData, response, responseError) in
            guard responseError == nil else {
                completion?(.failure(responseError!)) //<-
                return
            }
            
            if
                let data = responseData,
                let utf8Representation = String(data: data, encoding: .utf8)
            {
                print("response: ", utf8Representation)
                completion?(.success(utf8Representation)) //<-
            } else {
                print("no readable data received in response")
                completion?(.success("no readable data received in response")) //or you may want to pass .failure with your own error
            }
        }
        task.resume()
    }

You can use it like this:

        submitPost(post: myPost) { (result) in
            switch result {
            case .success(let value):
                print(value)
                //...use value here
            case .failure(let error):
                print(error)
                fatalError(error.localizedDescription)
            }
        }

Wow thanks for the answer, I had been searching for hours! I am still hitting an error though, I have a few buttons on the view that start disabled, then when the user successfully logs in, the buttons enable.


When I put

self.button.isEnabled = true

in the success case, I get a message

-[UIButton setEnabled:] must be used from main thread only


so I tried to do this:


submitPost(post: myPost) { (result) in

switch result {

case .success(let value):

print(value)

loginSuccessful = value

//...use value here

case .failure(let error):

print(error)

fatalError(error.localizedDescription)

}

}


if (loginSuccessful == "Success"

{

//enable Buttons

}


but while the print(value) does print "Success", I then try to set loginSuccessful to value, but loginSuccessful is "" outside the case and so the buttons never enable..


Thanks for the help!

Dont do this outside:


  submitPost(post: myPost) { (result) in
            switch result {
            case .success(let value):
                print(value)
                loginSuccessful = value
                //...use value here
               if (loginSuccessful == "Success"      {
                    //  enable Buttons HERE
               }          

            case .failure(let error):
                print(error)
                fatalError(error.localizedDescription)
            }
        }



Found this good detailed explanation:

h ttps://www.hackingwithswift.com/articles/161/how-to-use-result-in-swift

Thanks I'll check that out. When I put the button enable lines in the submitPost code, I get this error message:


-[UIButton setEnabled:] must be used from main thread only

Enclose it inside a dispatch to main queue:


DispatchQueue.main.async { () -> Void in
     // enable statement
}



PS: I think you should thank OOPer more than me for providing the solution…

Thanks to both of you!

Closure?
 
 
Q