So this code works for me in playground but for some reason URLSession.shared.dataTask(... doesnt call my flask api that im currently locally running. Any idea on what's wrong? So far I'm only concerned on why it does not enter the do{in my project but it works properly in playground.
func getWords() -> [Word]{
var words = [Word]()
let url = URL(string: self.url)
let request = URLRequest(url: url!)
let group = DispatchGroup()
print("XD")
URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
do {
print("A")
if let data = data{
print("B")
if let decodedResponse = try? JSONDecoder().decode([Word].self, from: data){
group.enter()
DispatchQueue.main.async(){
words = decodedResponse
print("C")
print(words)
group.leave()
}
}
}
print("DD")
} catch {
print("Words.swift Error in try catch")
}
group.enter()
}).resume()
group.leave()
group.notify(queue: DispatchQueue.main, execute: {
print(words)
})
print("ASDASD WORDS: \(words)")
for _ in 1 ... 4 {
// - to make sure there aren't duplicates -
var wordId:Int = Int.random(in: 0..<words.count)
while randomIds.contains(wordId){
wordId = Int.random(in: 0..<words.count)
}
randomIds.append(wordId)
}
//returns 4 words
return words
}
You aren't using DispatchGroup correctly; You should call enter before you start the asynchronous work and leave once it is complete. You can then use notify to perform some operation.
However, you don't really need a DispatchGroup in this situation; You have that because you are trying to turn an asynchronous operation into a synchronous one;
The correct approach is to accept that the operation is asynchronous and it isn't possible for this function to return [Word]. You will need to refactor the function to accept a completion handler closure and invoke that with the result.
Something like this:
func getWords(completionHandler:#escaping (Result<[Word], Error>) -> Void) -> Void{
var words = [Word]()
let url = URL(string: self.url)
let request = URLRequest(url: url!) // Note you should use a guard and call the completion handler with an error if url is `nil`
URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
if let error = error {
completionHandler(.failure(error))
} else {
do {
if let data = data {
let words = try JSONDecoder().decode([Word].self, from: data)
completionHandler(.success(words))
} else {
// TODO call completionHander with a .failure(SomeError)
}
} catch {
completionHandler(.failure(error))
}
}
}).resume()
}
Then you can call it:
getWords() { result in
switch result {
case .success(let words):
print(words)
case .failure(let error):
print(error.localizedDescription)
}
}
Related
I'm reading about GCD lately and trying to implement a series of network calls using the DispatchGroup, however, I'm am not seeing the desired results. If my understanding is correct whenever we use wait on the dispatch group it should block the thread until all enter and leaves are equal.In my case it not blocking the thread. Here is my piece of code
DispatchQueue.global(qos: .userInitiated).async {
let dispatchGroup = DispatchGroup()
print("herer \(Thread.current)")
var userAvatorsToLoad: [String] = []
let url = URL(string:"https://api.github.com/users")!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let response = response as? HTTPURLResponse , response.statusCode == 200 {
print("her\(Thread.current)")
let userResponse = try? JSONDecoder().decode([Users].self, from: data!)
userAvatorsToLoad.append(contentsOf:[userResponse![0].avatar_url,
userResponse![1].avatar_url,
userResponse![2].avatar_url])
userAvatorsToLoad.forEach {[weak self] (imageUrl) in
dispatchGroup.enter()
self?.loadImage(url: imageUrl) {
print("image Successfully cached")
dispatchGroup.leave()
}
}
}
}.resume()
dispatchGroup.wait()
print("hello")
}
In the output, I'm seeing hello even before any of my async operations are performed. Am i missing anything
The problem is that by the time you hit the wait call, it hadn’t yet encountered the forEach loop which was performing the enter and leave calls.
If you’re going to wait for an asynchronous request which is using the enter/leave calls, you’ll need to add a enter/leave for the main asynchronous request as well.
DispatchQueue.global(qos: .userInitiated).async {
let group = DispatchGroup()
var userAvatorsToLoad: [String] = []
let url = URL(string:"https://api.github.com/users")!
group.enter() // ADD THIS ...
URLSession.shared.dataTask(with: url) { (data, response, error) in
defer { group.leave() } // ... AND THIS
if let response = response as? HTTPURLResponse , response.statusCode == 200 {
print("her\(Thread.current)")
let userResponse = try? JSONDecoder().decode([Users].self, from: data!)
userAvatorsToLoad.append(contentsOf:[userResponse![0].avatar_url,
userResponse![1].avatar_url,
userResponse![2].avatar_url])
userAvatorsToLoad.forEach {[weak self] (imageUrl) in
group.enter()
self?.loadImage(url: imageUrl) {
print("image Successfully cached")
group.leave()
}
}
}
}.resume()
group.wait()
print("hello")
}
But, you should generally avoid wait. Use notify.
let url = URL(string:"https://api.github.com/users")!
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let response = response as? HTTPURLResponse,
200 ..< 300 ~= response.statusCode,
let data = data,
let userResponse = try? JSONDecoder().decode([Users].self, from: data)
else {
return
}
let userAvatorsToLoad = [
userResponse[0].avatar_url,
userResponse[1].avatar_url,
userResponse[2].avatar_url
]
let group = DispatchGroup()
userAvatorsToLoad.forEach { imageUrl in
group.enter()
self.loadImage(url: imageUrl) {
print("image Successfully cached")
group.leave()
}
}
group.notify(queue: .main) {
print("Hello")
}
}.resume()
This avoids blocking one of the very limited GCD worker threads while you perform the request. It also completely eliminates the need for the global concurrent queue at all.
You have to move "dispatchGroup.enter()" before the "URLSession.shared.dataTask" methods will be called.
So the code will be:
DispatchQueue.global(qos: .userInitiated).async {
let dispatchGroup = DispatchGroup()
print("herer \(Thread.current)")
var userAvatorsToLoad: [String] = []
let url = URL(string:"https://api.github.com/users")!
dispatchGroup.enter()
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let response = response as? HTTPURLResponse , response.statusCode == 200 {
print("her\(Thread.current)")
let userResponse = try? JSONDecoder().decode([Users].self, from: data!)
userAvatorsToLoad.append(contentsOf:[userResponse![0].avatar_url,
userResponse![1].avatar_url,
userResponse![2].avatar_url])
userAvatorsToLoad.forEach {[weak self] (imageUrl) in
dispatchGroup.enter()
self?.loadImage(url: imageUrl) {
print("image Successfully cached")
dispatchGroup.leave()
}
}
}
dispatchGroup.leave()
}.resume()
dispatchGroup.wait()
print("hello")
}
I am implementing a Networking Layer in Swift. Here is one of the functions. The function works as expected but I want to improve upon it. I am using DispatchQueue to make sure that the callback from the network client is always on the main thread. This ends up repeating the DispatchQueue.main.async in 3 different places.
Also, when I encounter some error when performing the request I still send back nil but as a success.
func getAllStocks(url: String, completion: #escaping (Result<[Stock]?,NetworkError>) -> Void) {
guard let url = URL(string: url) else {
completion(.failure(.invalidURL)) // wrap in DispatchQueue also
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completion(.success(nil)) // should I send nil or some sort of failure
}
return
}
let stocks = try? JSONDecoder().decode([Stock].self, from: data)
DispatchQueue.main.async {
completion(.success(stocks))
}
}
}
How can I minimize the code or is it fine?
The goal of the Result type is that you return a non-optional type on success and an error on failure.
I recommend to call completion on the current thread and dispatch the result on the caller side.
And handle also the DecodingError
func getAllStocks(url: String, completion: #escaping (Result<[Stock],Error>) -> Void) {
guard let url = URL(string: url) else {
completion(.failure(NetworkError.invalidURL))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error { completion(.failure(error)); return }
// if error is nil then data has a value
do {
let stocks = try JSONDecoder().decode([Stock].self, from: data!)
completion(.success(stocks))
} catch {
completion(.failure(error))
}
}.resume()
}
getAllStocks(url: someURL) { result in
DispatchQueue.main.async {
switch result {
case .success(let stocks): print(stocks)
case .failure(let networkError as NetworkError): handleNetworkError(networkError)
case .failure(let decodingError as DecodingError): handleDecodingError(decodingError)
case .failure(let error): print(error)
}
}
}
Lean into the build-in constructs and standard types.
func getAllStocks(url: String, completion: #escaping (Result<[Stock], Error>) -> Void) {
func completeOnMain(_ result: Result<[Stock], Error>) { // <-- Nested function
DispatchQueue.main.async { completion(result) } // <-- Handle repeated work
}
guard let url = URL(string: url) else {
completeOnMain(.failure(URLError(.badURL))) // <-- Standard Error
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
do {
if let error = error { throw error }
guard let data = data else { throw URLError(.badServerResponse) }
let stocks = try JSONDecoder().decode([Stock].self, from: data)
completeOnMain(.success(stocks))
} catch {
completeOnMain(.failure(error)) // <-- Unified error handling
}
}
}
A nested function is used to do the repeated work of dispatching to the main thread.
Standard error are used instead of defining custom errors.
A do/catch and throws are used to handle all the errors at once.
I have one final note: Async functions should always be async. The bad URL error should not call completion(_:) directly; use DispatchQueue.main.async to make sure the call happens in a later run loop.
I'm trying to implement many concurrent api calls through URLSession.shared singleton. What I want is that they are made concurrently so when I had all the responses, then execute the completion handler (I took this as example: https://medium.com/#oleary.audio/simultaneous-asynchronous-calls-in-swift-9c1f5fd3ea32):
init(completion: (()->Void)? = nil) throws {
self.myToken = myToken
let group = DispatchGroup()
group.enter()
print("Downloading User")
self.downloadUser(completion: { userObject in
self.userObject = userObject
group.leave()
})
group.enter()
print("Downloading Tickets")
self.downloadTickets(completion: { ticketsObject in
if let ticketsObject = ticketsObject {
self.ticketsObject = ticketsObject
}
group.leave()
})
group.wait()
completion?()
}
Then the functions the implement the api calls are something like:
private func downloadUser( completion: #escaping ((myUsuario)->Void )) {
let url = URL(string: "\(Globals.laravelAPI)me")
var request = URLRequest(url: url!)
let task = URLSession.shared.dataTask(with: request) {
(data:Data?, response:URLResponse?, error:Error?) in
if let error = error {
...
}
guard let httpResponse = response as? HTTPURLResponse,(200...299).contains(httpResponse.statusCode) else { ... }
if let data = data {
do {
let user = try JSONDecoder().decode(myUsuario.self, from: data)
completion(user)
} catch {
print("USER PARSING ERROR: \(error)")
fatalError()
}
}
}
task.resume()
}
When I run the program, it make the calls but never get the responses so the group.wait() is never executed.
I have array of urls, there can be anywhere from 1 to 6 images inside the array, never more then that.
The curent loop I'm using now works but the UrlSession moves ridiculously slow. It takes almost 1-2 minutes to upload 6 images to Firebase. I've tried it with while running Xcode and while not running Xcode and no matter what it moves really slow. My network is fast so it's not that.
I've read multiple SO posts that says the key is to put the completionHandler on the main queue which is what I've tried to no avail.
Is there a way I can execute multiple UrlSessions all at once instead of looping through them?
This works but moves slowwwww:
var urls = [URL]()
picUUID = UUID().uuidString
dict = [String:Any]()
let myGroup = DispatchGroup()
var count = 0
for url in urls{
myGroup.enter() // enter group here
URLSession.shared.dataTask(with: url!, completionHandler: {
(data, response, error) in
guard let data = data, let _ = error else { return }
DispatchQueue.main.async{
self.firstLoopUrlsToSendDataToStorage("\(self.picUUID)_\(self.count).jpg", picData: data)
self.count += 1
}
}).resume()
// send dictionary data to firebase when loop is done
myGroup.notify(queue: .global(qos: .background)) {
self.secondWhenLoopIsFinishedSendDictToFirebaseDatabase()
self.count = 0
}
}
func firstLoopUrlsToSendDataToStorage(_ picId: String, picData: Data?){
dict.updateValue(picId, forKey:"picId_\(count)")
let picRef = storageRoot.child("pics")
picRef.putData(picData!, metadata: nil, completion: { (metadata, error) in
if let picUrl = metadata?.downloadURL()?.absoluteString{
self.dict.updateValue(picUrl, forKey:"picUrl_\(count)")
self.myGroup.leave() // leave group here
}else{
self.myGroup.leave() // leave group if picUrl is nil
}
}
}
func secondWhenLoopIsFinishedSendDictToFirebaseDatabase(){
let ref = dbRoot.child("myRef")
ref.updateChildValues(dict, withCompletionBlock: { (error, ref) in
DispatchQueue.main.async{
self.displaySuccessAlert()
}
}
}
I looked at this guy's Medium post ands thinking of combining it with a switch statement since there can only be 1-6 urls but I can't figure out a way to know when all of them are finished so that I can run my secondWhenLoopIsFinishedSendDictToFirebaseDatabase() function
fileprivate func multipleUsrlTaskSessionsAtOnce(){
switch userComplaintImageUrls.count {
case 0:
let urlZero = userComplaintImageUrls[0]
TaskManager.shared.dataTask(with: url) { (data, response, error) in
// how to know when all of them are completed?
}
break
case 1:
let urlOne = userComplaintImageUrls[1]
TaskManager.shared.dataTask(with: urlOne ) { (data, response, error) in
// how to know when all of them are completed?
}
break
case 2:
let urlTwo = userComplaintImageUrls[2]
TaskManager.shared.dataTask(with: urlTwo) { (data, response, error) in
// how to know when all of them are completed?
}
break
case 3:
let urlThree = userComplaintImageUrls[3]
TaskManager.shared.dataTask(with: urlThree) { (data, response, error) in
// how to know when all of them are completed?
}
break
case 4:
let urlFour = userComplaintImageUrls[4]
TaskManager.shared.dataTask(with: urlFour) { (data, response, error) in
// how to know when all of them are completed?
}
break
case 0:
let urlFive = userComplaintImageUrls[5]
TaskManager.shared.dataTask(with: urlFive) { (data, response, error) in
// how to know when all of them are completed?
}
break
default:
break
}
}
TaskManager Class:
class TaskManager {
static let shared = TaskManager()
let session = URLSession(configuration: .default)
typealias completionHandler = (Data?, URLResponse?, Error?) -> Void
var tasks = [URL: [completionHandler]]()
func dataTask(with url: URL, completion: #escaping completionHandler) {
if tasks.keys.contains(url) {
tasks[url]?.append(completion)
} else {
tasks[url] = [completion]
let _ = session.dataTask(with: url, completionHandler: { [weak self] (data, response, error) in
DispatchQueue.main.async {
print("Finished network task")
guard let completionHandlers = self?.tasks[url] else { return }
for handler in completionHandlers {
print("Executing completion block")
handler(data, response, error)
}
}
}).resume()
}
}
}
Is there a way I can execute multiple UrlSessions all at once instead of looping through them?
Just the opposite. For optimal speed, you want one URLSession that you configure and store (i.e. not the shared session), that is used for all your different tasks. The session will be able to schedule these and reduce your latency.
I have a function that sends a GET request to an API and I need to wait for the response before I can continue with the rest of my code.
I have tried various different threads but haven't got it to work. I've been trying to solve this by myself but I am quite confused by threads and so fourth.
These are my functions:
func checkDomains() {
let endings = [".com"]
for ending in endings {
let domainName = names[randomNameIndx] + ending
let urlString = "https://domainr.p.mashape.com/v2/status?mashape-key={my-key}" + domainName
let url = URL(string: urlString)
var request = URLRequest(url: url!)
request.setValue("{my-key}", forHTTPHeaderField: "X-Mashape-Key")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.httpMethod = "GET"
var ourBool = false
DispatchQueue.main.sync {
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, er) in
do {
print("hey")
if let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as? NSDictionary {
ourBool = String(describing: json).contains("inactive")
if ourBool {
self.domainStatuses.append("available")
} else {
self.domainStatuses.append("taken")
}
}
} catch {
}
})
task.resume()
}
}
}
#objc func btnTapped(sender: UIButton) {
self.prepareForNewName()
sender.shake(direction: "horizontal", swings: 1)
self.checkDomains() // This needs to finish before calling the next functions
self.setupForNewName()
self.animateForNewName()
}
My suggestion is in adding callback param into your async function. Example below:
func checkDomains(_ onResult: #escaping (Error?) -> Void)
Then you can call onResult inside your function in places where server return success result or error.
func checkDomains(_ onResult: #escaping (Error?) -> Void) {
...
let task = URLSession.shared.dataTask ... {
do {
//Parse response
DispatchQueue.main.async { onResult(nil) }
} catch {
DispatchQueue.main.async { onResult(error) }
}
...
}
}
The last thing you need pass callback param in place where you calling checkDomains function
#objc func btnTapped(sender: UIButton) {
self.prepareForNewName()
sender.shake(direction: "horizontal", swings: 1)
self.checkDomains { [unowned self] error in
if let error = error {
// handle error
return
}
self.setupForNewName()
self.animateForNewName()
}
}
Thank you for your answers. I just came home and realized there is a simpler way of doing this.
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
do {
...
if response != nil {
DispatchQueue.main.async {
self.setupForNewName()
self.animateForNewName()
}
}
}
} catch {
print(error)
}
})
task.resume()
Of course this is dependent on the response of the API but I am confident enough the API will always respond.
Lately it is a good practice to do network call asynchronously. There some technics to make code simpler, like https://cocoapods.org/pods/ResultPromises
TBC...