Home > Mobile >  Swift Concurrency UIButton
Swift Concurrency UIButton

Time:05-24

Is it possible to use async/await for POST HTTP requests in UIKit, namely a from UIButton?

I can get the request sent but then it immediately crashes. Working perfectly using URLSession, using this as a learning experience.

lazy var buttonTest: UIButton = {
    let button = UIButton()
    button.addTarget(self, action: #selector(testAsyncTwo), for: .touchUpInside)
    return button
}()

@objc func testAsync() async throws {
    let date = Date()
    let df = DateFormatter()
    df.dateFormat = "yyyy-MM-dd HH:mm:ss"
    let dateString = df.string(from: date)

    let json: [String: Any] = ["item": "1",
                               "time": "\(dateString)"]
    let jsonData = try? JSONSerialization.data(withJSONObject: json)
    
    guard let url = URL(string: "https://myapiendpoint.com/api/") else { fatalError("Missing URL") }
    var urlRequest = URLRequest(url: url)
    let token: String = "mySecretKey"
    urlRequest.setValue("Token \(token)", forHTTPHeaderField: "Authorization")
    urlRequest.httpMethod = "POST"
    urlRequest.setValue("\(String(describing: jsonData?.count))", forHTTPHeaderField: "Content-Length")
    urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
    urlRequest.httpBody = jsonData
    let (data, response) = try await URLSession.shared.data(for: urlRequest)
    
    guard (response as? HTTPURLResponse)?.statusCode == 200 else { fatalError("Error while fetching data") }
    let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
    if let responseJSON = responseJSON as? [String: Any] {
        print(responseJSON) //Code after Successfull POST Request
    }
}

Thanks very much!

CodePudding user response:

The crash is unrelated to your URLSession code. The problem is that you have declared the button’s selector to be asynchronous throwing function. That is incompatible with an @objc selector.

Replace:

@objc func testAsync() async throws { 
    ... 
}

With:

@objc func didTapButton(_ sender: Any) {
    Task {
        try await testAsync()
    }
}

func testAsync() async throws {
    ...
}

The key observation is that the @objc method is not an asynchronous function. I would also give the button handler a sender parameter and a more descriptive name, to follow established conventions and make it clear at a glance that it is handling a button tap.

Obviously, when you do this, you will have to change the #selector reference, too:

button.addTarget(self, action: #selector(didTapButton(_:)), for: .touchUpInside)
  • Related