Reputation: 196
I need to realise synchronous calls (GET and POST) to a rest API in Swift. I know this question has been asked before, but all solutions that I found focus on using one of the async methods (like URLSession) and building some kind of wrapper (e.g. using Semaphore), that waits for the process to be finished and then proceeds with the program. This is unnecessary complex, so I wondered if there isn't an easier solution available like in many other programming languages. For example, in Python you can achieve the same thing with only one line of code (ok, you have to include the requests library for that):
response = requests.get("https://jsonplaceholder.typicode.com/posts")
Is there such possibility (or Framework) for Swift? What would be your go-to-approach in 2021?
EDIT: Maybe I should have mentioned this before: the use case is a command line application for a very specific enterprise use case. There is no UI involved. But as many already absolutely correctly stated in the answers: normally, it's absolutely not the way to go.
Upvotes: 0
Views: 3265
Reputation: 285270
With the Combine
framework – introduced in 2019 – you can get an asynchronous network request in a quasi synchronous notation.
import Combine
var cancellable : AnyCancellable?
struct Post : Decodable, Identifiable {
let userId, id: Int
let title, body: String
}
cancellable = URLSession.shared.dataTaskPublisher(for: URL(string: "https://jsonplaceholder.typicode.com/posts")!)
.map(\.data)
.decode(type: [Post].self, decoder: JSONDecoder())
.sink(receiveCompletion: { result in
print(result)
}, receiveValue: { posts in
print(posts)
})
Upvotes: 2
Reputation: 52632
You need to realise synchronous calls to a server somewhere on the network? You realise that calls can fail for example with a timeout after 60 seconds, forcing your synchronous call to wait for 60 seconds?
The advice in 2021 is: Don’t do it. If you use Swift 5.5 you can do something that looks like a synchronous call, basically doing the semaphore wait/signal approach or something similar behind the scenes.
After any http call returning, it would be unacceptable to keep the user waiting for the call to return, so the context of the call may have changed. For example you download a photo to be displayed in a cell of a table view, and when the call returns, the cell has been scrolled out and the cell object is reused for another tow of the table.
Upvotes: 1
Reputation: 299623
let response = try Data(contentsOf: url)
As long as this is a command-line app, this is fine (and generally even pretty good). If this is an iOS app, this can crash the program if it takes too long to return, so don't do that. Use the async methods. They are not too complex; they solve the required problem. (This can be done on background queues, but generally shouldn't be. It causes thread-explosion.)
If you are using Swift 5.5 with async/await, then use URLSession.bytes(for:)
:
let response = await try urlSession.bytes(for: url)
This is appropriate in any async code. (It is not a "synchronous call," but it is written in that way.)
There is no solution where you can fetch data from the network in synchronous code on iOS on the main thread. That's by design. You must never block the main thread. No library or framework can change that. It would even be true in Python if you were using Python on an event loop that drives a UI. It's not a language issue. It's the fact that the main runloop needs to keep processing or the UI will hang.
Upvotes: 5