3

I'm trying to send my API multiple photos iterating an image array. I want to send each image in a different thread but when the fist thread finish it doesn't wait for the rest of the threads and only one picture is sended.

This is my code:

// create the concurrent queue
    let asyncQueue = DispatchQueue(label: "asyncQueue", attributes: .concurrent)

    // perform the task asynchronously
    for image in self.arrayImages{
        asyncQueue.async() {
            let strBase64:String = image.base64EncodedString(options: .lineLength64Characters)
            self.sendImage(image: strBase64)
        }

    }

I've recently started programming in Swift and I don't know how to synchronize threads. Could you help me, please?

The code to do my requests is:

// Función para mandar a la API la petición de añadir una imagen a la idea
func sendImage(image:String){
    let data = NSMutableDictionary()

    let id:Int = (idea?.id)!
    let pasoAct = idea?.pasoActual
    data["id_idea"] = id
    data["id_paso"] = pasoAct
    data["contenido"] = image

    let jsonData = try! JSONSerialization.data(withJSONObject: data, options: JSONSerialization.WritingOptions(rawValue: 0))
    let request = NSMutableURLRequest(url: NSURL(string:"http://192.168.2.101:3001/api/imagen") as! URL)
    request.httpMethod = "POST"
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accept")
    request.httpBody = jsonData

    URLSession.shared.dataTask(with: request as URLRequest, completionHandler: self.responseImage).resume()
}
// Función de control de la respuesta del servidor tras enviar una imagen
func responseImage(data: Data?, response: URLResponse?, error: Error?) {

    if (error != nil) {
        print("No se ha podido contactar con el servidor")
    }
    else{
        // Connected, check response, GET status codes.
        let statusCode = (response as! HTTPURLResponse).statusCode

        switch statusCode{
            case 200:
                print("Imagen subida")

            default:
                print("Error en la petición al servidor")
        }
    }
}

The first image is uploaded correctly (response returns status code=200) but next images don't

This is the code I've modified following your advice:

func sendImage(image:String, completion: @escaping () -> Void) {

    let data = NSMutableDictionary()

    let id:Int = (idea?.id)!
    let pasoAct = idea?.pasoActual
    data["id_idea"] = id
    data["id_paso"] = pasoAct
    data["contenido"] = image

    let jsonData = try! JSONSerialization.data(withJSONObject: data, options: JSONSerialization.WritingOptions(rawValue: 0))
    let request = NSMutableURLRequest(url: NSURL(string:"http://192.168.2.101:3001/api/imagen") as! URL)
    request.httpMethod = "POST"
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accept")
    request.httpBody = jsonData

    URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
        self.responseImage(data: data, response: response, error: error)

        // When responseImage is complete, call the completion handler
        completion()
    }
}


// And the following code is called when I try to send the array of images
let group = DispatchGroup()
    let asyncQueue = DispatchQueue(label: "asyncQueue", attributes: .concurrent)
    for image in self.arrayImages {

        asyncQueue.async {
            group.enter()

            let strBase64:String = image.base64EncodedString(options: .lineLength64Characters)
            self.sendImage(image: strBase64) {
                group.leave()
            }
        }
    }

    group.wait()

2 Answers 2

2

In your case, it may be better to use concurrentPerform:

DispatchQueue.concurrentPerform(iterations: self.arrayImages.count) { i in
    let strBase64 = self.arrayImages[i].base64EncodedString(options: .lineLength64Characters)
    self.sendImage(image: strBase64)
}

print("Done") // This line will only be executed after self.send() is
              // called on all images. However, this does not mean that
              // the server has received all the images.
Sign up to request clarification or add additional context in comments.

1 Comment

agreed. in case all the asyncs basically execute the same code, I'd also recommend this
2

You can use a DispatchGroup

let group = DispatchGroup()

for image in self.arrayImages {

    let workItem = DispatchWorkItem(qos: .default, flags: []) {
        let strBase64:String = image.base64EncodedString(options: .lineLength64Characters)
        self.sendImage(image: strBase64)
    }
    // perform the task asynchronously
    group.notify(queue: asyncQueue, work: workItem)
}

group.wait()

// Done!!

EDIT: Now you've updated your question, I can see you're making another async call. You can handle that as follows:

func sendImage(image:String, completion: @escaping () -> Void) {

        // Your code here

        URLSession.shared.dataTask(with: URL()) { (data, response, error) in
            self.responseImage(data: data, response: response, error: error)

            // When responseImage is complete, call the completion handler
            completion()
         }

}

let group = DispatchGroup()

for image in self.arrayImages {

    asyncQueue.async {
        group.enter()

        let strBase64:String = image.base64EncodedString(options: .lineLength64Characters)
        self.sendImage(image: strBase64) {
            group.leave()
        }
    }
}

group.wait()

// Done!!

A simple rule I like to follow, is to always add a completion handler as a parameter to every async func that I write.

4 Comments

while in this case id use DispatchQueue.concurrentPerform this answer has value in showing a general approach
didn't work for me. The first pic is uploaded an then the response to my API fails
I send my images as a 64Bytes string to my API, and I need to start a thread for each request because my API throws a "request entity too large" error
I've followed your advice but it doesn't seem to work for me. I've only changed a line because I need to send a request instead an URL. I'm going to edit my question so you can watch it

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.