Reputation: 6690
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
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