Search code examples
swiftdispatch-queue

Swift 3 - Function Inside DispatchQueue


I called a function inside DispatchQueue.main.async. Here's my code:

let group = DispatchGroup()
group.enter()

DispatchQueue.main.async {
        for i in 0 ... (Global.selectedIcons.count - 1) {
            if self.albumorphoto == 1 {
                if i == 0 {
                    self.detector = 1
                    self.uploadPhoto() //here
                }
                else {
                    self.detector = 2
                    self.uploadPhoto() //here
                }
            }
            else {
                self.uploadPhoto() //here
            }
        }
        group.leave()
    }

    group.notify(queue: .main) {
        print("done")
    }
}

func uploadPhoto(){
    var request = URLRequest(url: URL(string: url)!)
    request.httpMethod = "POST"
    request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")

    let params = param

    request.httpBody = params.data(using: String.Encoding.utf8)

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard let data = data, error == nil else {
            print("error=\(error!)")
            return
        }

        if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
            print("statusCode should be 200, but is \(httpStatus.statusCode)")
            print("response = \(response!)")
        }

        let responseString = String(data: data, encoding: .utf8)
        print("responseString = \(responseString!)")

        if self.detector == 1 {
            self.album = self.parseJsonData(data: data)
        }

    }

    task.resume()
}

func parseJsonData(data: Data) -> [AnyObject] {
    do {
        let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary
        let jsonalbum = jsonResult!["data"] as? [AnyObject]
        for jsonAlbum in jsonalbum! {
            self.folderID = jsonAlbum["id"] as! String
        }
    } catch {
        print(error)
    }

    return album
}

I wish to make it wait until all the tasks in DispathcQueue finish. It works but the problem is my function uploadPhoto(). It can't wait until uploadPhoto() finish doing its task. Any idea to solve this? Thanks!


Solution

  • Using a DispatchGroup is the right choice here, but you have to enter and leave for each asynchronous task:

    let group = DispatchGroup()
    photos.forEach { photo in
    
        group.enter()
        // create the request for the photo
    
        URLSession.shared.dataTask(with: request) { data, response, error in
    
            group.leave()
            // handle the response
    
        }.resume()
    }
    group.notify(queue: .main) {
        print("All photos uploaded.")
    }
    

    You don't need a DispatchQueue.async() call because URLSession.shared.dataTask is already asynchronous.

    In my code i assumed that you want to model your objects as Photo and replace Global.selectedIcons.count with a photos array:

    class Photo {
        let isAlbum: Bool
        let isDefector: Bool
        let imageData: Data
    }
    

    I'd recommend you take a look at Alamofire and SwiftyJSON to further improve your code. These are popular libraries that make dealing with network requests a lot easier. With them you can reduce almost the entire uploadPhoto()/parseJsonData() functions to something like this:

    Alamofire.upload(photo.imageData, to: url).responseSwiftyJSON { json in
        json["data"].array?.compactMap{ $0["id"].string }.forEach {
            self.folderID = $0
        }
    }
    

    This makes your code more stable because it removes all forced unwrapping. Alamofire also provides you with features like upload progress and resuming & cancelling of requests.