GarySabo
GarySabo

Reputation: 6690

Dispatch Groups not working as expected

I set up a similar function to this and it works fine, however I'm crashing on group.leave() here, and in testing I noticed that 1 or 2 of the print lines print("link = \(link)") is being printed after print("getRekos processing over, handler is called"), in other words group.leave() is being called prior to full completion of the for loop or some of the iterations are "leaking" outside of the group. What could be causing this? I've posted the code for getExpandedURL and my custom extension resolveWithCompletionHandler which are used inside getRekos

 public func getRekos(rekoType: RekoCategory, handler: @escaping (Set<URL>) -> Void) {

        let setOfLinks = Set<URL>()
        let group = DispatchGroup() //Dispatch group code from: http://stackoverflow.com/questions/38552180/dispatch-group-cannot-notify-to-main-thread
        let backgroundQ = DispatchQueue.global(qos: .default)


        TwitterRequest().fetchTweets(searchType: rekoType) { result in

            guard let tweets = TWTRTweet.tweets(withJSONArray: result) as? [TWTRTweet] else { print("error in getRekos casting TwitterRequest().fetchTweets result as [TWTRTweet]"); return }

            for tweet in tweets {

                let text = tweet.text

                group.enter()
                backgroundQ.async(group: group,  execute: {

                    //Check tweet text if contains any URLs
                    self.getExpandedURLsFromText(text: text, handler: { (getExpandedLinksResult, error) in

                        if error != nil {
                            group.leave()
                        } else {

                            for link in getExpandedLinksResult {
                                print("link = \(link)")

                            }
                            group.leave()
                        }
                    })
                })
            } //for tweet in tweets loop
            group.notify(queue: backgroundQ, execute: {
                DispatchQueue.main.async {
                    print("getRekos processing over, handler is called")
                    handler(setOfLinks)
                }
            })
        }
    }


private func getExpandedURLsFromText(text: String, handler: @escaping ([URL], Error?) -> Void) {

    var linksInText = [URL]()
    let group = DispatchGroup() //Dispatch group code from: http://stackoverflow.com/questions/38552180/dispatch-group-cannot-notify-to-main-thread
    let backgroundQ = DispatchQueue.global(qos: .default)


    let types: NSTextCheckingResult.CheckingType = .link
    let detector = try? NSDataDetector(types: types.rawValue)

    guard let detect = detector else { print("NSDataDetector error"); return }

    let matches = detect.matches(in: text, options: .reportCompletion, range: NSMakeRange(0, (text.characters.count)))

    //CRITICAL CHECK - results were getting lost here, so pass back error if no match found
    if matches.count == 0 {
        handler([], RekoError.FoundNil("no matches"))
    }
        // Iterate through urls found in tweets
        for match in matches {

            if let url = match.url {

                    guard let unwrappedNSURL = NSURL(string: url.absoluteString) else {print("error converting url to NSURL");continue}

                    group.enter()
                    backgroundQ.async(group: group,  execute: {

                        //Show the original URL (ASYNC)
                        unwrappedNSURL.resolveWithCompletionHandler(completion: { (resultFromResolveWithCompletionHandler) in

                            guard let expandedURL = URL(string: "\(resultFromResolveWithCompletionHandler)") else {print("couldn't covert to expandedURL"); return}

                            linksInText.append(expandedURL)
                            group.leave()
                            //handler(linksInText, nil)
                        })
                    })


            } else { print("error with match.url") }
        } //for match in matches loop
            group.notify(queue: backgroundQ, execute: {
                DispatchQueue.main.async {
                    //print("getExpandedURLsFromText processing over, handler is called")
                    handler(linksInText, nil)
                }
            })
}

// Create an extension to NSURL that will resolve a shortened URL
extension NSURL
{
    func resolveWithCompletionHandler(completion: @escaping (NSURL) -> Void) {
        let originalURL = self
        let req = NSMutableURLRequest(url: originalURL as URL)
        req.httpMethod = "HEAD"

        URLSession.shared.dataTask(with: req as URLRequest){body, response, error in

            if error != nil {
                print("resolveWithCompletionHandler error = \(error)")
            }

            if response == nil {
                print("resolveWithCompletionHandler response = nil")
            }
            completion(response?.url as NSURL? ?? originalURL)
            }
            .resume()
    }
}

Upvotes: 0

Views: 895

Answers (1)

Sulthan
Sulthan

Reputation: 130102

If there are multiple matches (matches.count > 1) in getExpandedURLsFromText then the result handler is called multiple times, making your dispatch group logic break up.

Clean up your logic.

You have to make sure that the completion handler is always called exactly once when successful and exactly once for each error. You are missing calls to completion handler in multiple places in your guard statements.

Upvotes: 1

Related Questions