I have a service class that makes an api call and stores data into its property. Then my interactor class have a method where I want to make service class api call and when data will be stored - return it. I tried myself to handle this with completion handler and dispatch group, but (I suppose I just missing something) this didn't work. I would be very appreciated if you help me to deal with this problem. Thanks in advance!
Service class:
class PunkApiService{
var beers = [Beer]()
func loadList(at page: Int){
//MARK: - Checks is URL is valid pagination
guard let url = URL(string: "https://api.punkapi.com/v2/beers?page=\(page)&per_page=25") else {
print("Invalid URL")
return
}
//MARK: - Creating URLSession DataTask
let task = URLSession.shared.dataTask(with: url){ data, response, error in
//MARK: - Handling no erros came
guard error == nil else {
print(error!)
return
}
//MARK: - Handling data came
guard let data = data else{
print("Failed to load data")
return
}
do{
let beers = try JSONDecoder().decode([Beer].self, from: data)
self.beers.append(contentsOf: beers)
}
catch{
print("Failed to decode data")
}
}
task.resume()
}
And Interactor class(without completion handler or dispatch group):
class BeersListInteractor:BeersListInteractorProtocol{
private var favoriteBeers = FavoriteBeers()
private var service = PunkApiService()
//MARK: - Load list of Beers
func loadList(at page: Int) -> [Beer]{
service.loadList(at: page)
return service.beers
}
Added: my attempt with completion handler
var beers: [Beer]
func loadList(at page: Int, completion: ()->()){
service.loadList(at: page)
completion()
}
func completion(){
beers.append(contentsOf: service.beers)
}
loadList(at: 1) {
completion()
}
CodePudding user response:
This is what async/await
pattern is for, described here. In your case both loadList
functions are async
, and the second one await
s for the first one:
class PunkApiService {
func loadList(at page: Int) async {
// change function to await for task result
let (data, error) = try await URLSession.shared.data(from: url)
let beers = try JSONDecoder().decode([Beer].self, from: data)
...
return beers
}
}
class BeersListInteractor: BeersListInteractorProtocol {
func loadList(at page: Int) async -> [Beer]{
let beers = await service.loadList(at: page)
return service.beers
}
}
See a good explanation here
CodePudding user response:
I think that you were on the right path when attempting to use a completion block, just didn't do it correctly.
func loadList(at page: Int, completion: @escaping ((Error?, Bool, [Beer]?) -> Void)) {
//MARK: - Checks is URL is valid pagination
guard let url = URL(string: "https://api.punkapi.com/v2/beers?page=\(page)&per_page=25") else {
print("Invalid URL")
completion(nil, false, nil)
return
}
//MARK: - Creating URLSession DataTask
let task = URLSession.shared.dataTask(with: url){ data, response, error in
//MARK: - Handling no erros came
if let error = error {
completion(error, false, nil)
print(error!)
return
}
//MARK: - Handling data came
guard let data = data, let beers = try? JSONDecoder().decode([Beer].self, from: data) else {
completion(nil, false, nil)
return
}
completion(nil, true, beers)
}
task.resume()
}
This is the loadList
function, which now has a completion
parameter that will have three parameters, respectively the optional Error
, the Bool
value representing success or failure of obtaining the data, and the actual [Beers]
array, containing the data (if any was retrieved).
Here's how you would now call the function:
service.loadList(at: page) { error, success, beers in
if let error = error {
// Handle the error here
return
}
if success, let beers = beers {
// Data was correctly retrieved - and safely unwrapped for good measure, do what you need with it
// Example:
loader.stopLoading()
self.datasource = beers
self.tableView.reloadData()
}
}
Bear in mind the fact that the completion is being executed asynchronously, without stopping the execution of the rest of your app.
Also, you should decide wether you want to handle the error directly inside the loadList
function or inside the closure, and possibly remove the Error
parameter if you handle it inside the function.
The same goes for the other parameters: you can decide to only have a closure that only has a [Beer]
parameter and only call the closure if the data is correctly retrieved and converted.