Search code examples
iosswiftxmlasynchronousswift4

DispatchQueue does not wait for async function to complete


I'm attempting to create a simple package tracking app that uses the USPS API to fetch data. The callRestService method successfully fetches the data and its completion handler serviceCallback (which sets the unparsedXml property) works. However, the method that I call callRestService from does not wait for it and its completion handler to complete before moving on, resulting in my print(unparsedXml) statement returning nil.

As shown below, I tried to use a DispatchGroup object and DispatchQueue to make the function wait for callRestService's completion but it continues regardless. How can I make the function wait for the call to complete?

var unparsedXml:String?

public func getTrackingInfo(_ trackingNumber: String) -> TrackingInfo {
    let group = DispatchGroup()
    group.enter()
    DispatchQueue.global(qos: DispatchQoS.default.qosClass).async {
        self.callRestService(requestUrl: self.getRequest(trackingNumber))
        group.leave()
    }
    group.wait()
    print(unparsedXml)
    return TrackingInfo()
}

private func getRequest(_ trackingNumber: String) -> String {
    let APIUsername = "Intentionally Omitted"
    let trackingXmlLink = "http://production.shippingapis.com/ShippingAPI.dll?API=TrackV2&XML=%3CTrackFieldRequest%20USERID=%22" + APIUsername + "%22%3E%20%3CRevision%3E1%3C/Revision%3E%20%3CClientIp%3E127.0.0.1%3C/ClientIp%3E%20%3C/SourceId%3E%20%3CTrackID%20ID=%22" + trackingNumber + "%22%3E%20%3CDestinationZipCode%3E66666%3C/DestinationZipCode%3E%20%3CMailingDate%3E2010-01-01%3C/MailingDate%3E%20%3C/TrackID%3E%20%3C/TrackFieldRequest%3E"
    return trackingXmlLink
}

public func callRestService(requestUrl:String) ->Void
{
    var request = URLRequest(url: URL(string: requestUrl)!)
    request.httpMethod = "GET"
    
    let session = URLSession.shared
    let task = session.dataTask(with: request, completionHandler: serviceCallback)
    
    task.resume()
}

private func serviceCallback(data:Data? , response:URLResponse? , error:Error? ) -> Void
{
    unparsedXml = String(data: data!, encoding: .utf8)
    //print(unparsedXml) Works correctly when uncommented
}

Solution

  • Your problem is that callRestService will dispatch an asynchronous network operation, so your group.leave will be called immediately, firing your group.notify.

    You could put the group.leave in a completion handler, but you should avoid blocking code. I would suggest you structure your getTrackingInfo as asynchronous function that takes a completion handler:

    public func getTrackingInfo(_ trackingNumber: String, completion:(TrackingInfo?,Error?) -> Void) {
        self.callRestService(requestUrl: self.getRequest(trackingNumber)) { (data, response, error) in
            guard error == nil, let returnData = data else {
                completion(nil,error)
                return
            }
    
            completion(TrackingInfo(returnData),nil)
        }
    }
    
    private func getRequest(_ trackingNumber: String) -> String {
        let APIUsername = "Intentionally Omitted"
        let trackingXmlLink = "http://production.shippingapis.com/ShippingAPI.dll?API=TrackV2&XML=%3CTrackFieldRequest%20USERID=%22" + APIUsername + "%22%3E%20%3CRevision%3E1%3C/Revision%3E%20%3CClientIp%3E127.0.0.1%3C/ClientIp%3E%20%3CSourceId%3EFaiz%20Surani%3C/SourceId%3E%20%3CTrackID%20ID=%22" + trackingNumber + "%22%3E%20%3CDestinationZipCode%3E66666%3C/DestinationZipCode%3E%20%3CMailingDate%3E2010-01-01%3C/MailingDate%3E%20%3C/TrackID%3E%20%3C/TrackFieldRequest%3E"
        return trackingXmlLink
    }
    
    public func callRestService(requestUrl:String, completion:(Data? , URLResponse? , Error? ) -> Void) ->Void
    {
        var request = URLRequest(url: URL(string: requestUrl)!)
        request.httpMethod = "GET"
    
        let session = URLSession.shared
        let task = session.dataTask(with: request, completionHandler: completion)
    
        task.resume()
    }