Apologies
You must be signed in to watch this lesson.
Synchronizing Multiple Fetches using Dispatch Group
AppStore JSON APIs
To resolve the issue of unnecessary data fetches, we'll perform an initial load of all necessary data upfront in our first controller file. One problem you'll notice right away is that its quite difficult to figure out when the last piece of data was fetched. Fixing this is quite easy by using the DispatchGroup class. For each request we fire off, we'll make an enter() call, and upon completion we'll perform a leave(). Once all information is retrieved, we'll be notified and thats when we can reload our UI. This is a really fun and interesting lesson so let's get started!

Comments (14)
ravikanth.marri@gmail.com
5 years ago
Thanks for introducing dispatch group concept, its cool.
Yunio
5 years ago
dopee broooo
tsangaris
5 years ago
El profesor!
tsangaris
5 years ago
Brian, is there an elegant way to include the dispatchGroup inside the fetchAppGroup() helper as an optional? Then perform the dispatchGroup.enter() and dispatchGroup.leave() inside the fetchAppGroup(). I tried to do this, but trying to unwrap an optional using guard let statement, it requires to return, so the code stops from executing in case we don't want to use dispatchGroups. What i am trying to achieve here is: func fetchAppGroup(dispatchGroup: DispatchGroup? ,urlString: String, completion: @escaping (AppGroup?, Error?) -> Void) { var hasDispatchGroup = true guard let dispatchGroup = dispatchGroup else { hasDispatchGroup = false // this will throw an error since the compiler will ask you to either return or throw an error } if(hasDispatchGroup) { dispatchGroup.enter() } ..... and inside the "do" block: if(hasDispatchGroup) { dispatchGroup.leave() } } I am trying to make the method as flexible as possible to achieve DRY.
Brian Voong
5 years ago
ncytimothy
5 years ago
All, any good suggestions/books to learn more about DispatchGroup() please? Thank you!
Brian Voong
5 years ago
ncytimothy
5 years ago
Nice, will dive deeper!
eladshw17
5 years ago
I used eumn in the Service.swift just to make the code a little cleaner and more dynamic enum FeedType: String { case TopFree = "top-free" case TopGrossing = "top-grossing" case NewGames = "new-games-we-love" } func createUrlForFeedType(type:FeedType) -> URL? { let urlString = "https://rss.itunes.apple.com/api/v1/us/ios-apps/\(type.rawValue)/all/50/explicit.json" return URL(string: urlString) } func fetchByType(type: FeedType , completion: @escaping (AppGroup?, Error?) -> ()) { guard let url = createUrlForFeedType(type: type) else { return } URLSession.shared.dataTask(with: url) { (data, resp, err) in if let err = err { print ("Failed to fetch", err) completion(nil, err) return } guard let data = data else { return } do { let appGroup = try JSONDecoder().decode(AppGroup.self, from: data) completion(appGroup, nil) } catch { print ("faild to decode error", error) completion(nil,error) }}.resume() }
e.tavares
5 years ago
Hi! Mine is taking a long time to appear.Actually I am getting an error at console http load failed error code -999
ghandirekt
4 years ago
Why do we put dispatchGroup.leave() inside the Service function's code? How come group1 and group2 variables are being initialized despite these being set after dispatchGroup.leave()?
李承諴
4 years ago
hi Brian why you add .leave() in the begin of completion , not the end of it
GeeElle
4 years ago
Hi Brian, thanks for this video. Came here looking for one thing and walked away knowing about DispatchGroups which solved one of my other problems at the same time! Excellent stuff! Keep up the good work, it's much appreciated! -- G
johnrm9
4 years ago
Something you might find interesting in the fetchData function is the use of compactMap in dispatchGroup.notify: var groups = [AppGroup]() func fetchData() { var group1, group2, group3: AppGroup? let dispatchGroup = DispatchGroup() ... dispatchGroup.notify(queue: .main) { self.groups = [group1, group2, group3].compactMap {$0} // compact out any nil valued group // The above statement has the same effect as // if let group = group1 { // self.groups.append(group) // } // if let group = group2 { // self.groups.append(group) // } // if let group = group3 { // self.groups.append(group) // } self.collectionView.reloadData() } }
Brian Voong
4 years ago
Mohamed Ibrahim Fetyany
4 years ago
hey brian why we need to create group1, group2 and group3 we can direct in every fetch API append them to groups and dispatcherGroup.notify reload collection view
Brian Voong
4 years ago
Kritbovorn Taweeyossak
4 years ago
$ work for me. var urlStrings = ["url1", "url2", "url3", "url4", "url5"] // FIXME: fetchedData() fileprivate func fetchedData() { // Use for Fetched data together DispatchGroup() let disPatchGroup = DispatchGroup() // Looping for urlString in urlStrings { disPatchGroup.enter() // start include APIService.shared.fetchedAppGroup(urlString: urlString) { (appGroup, err) in disPatchGroup.leave() // stop include if let error = err { print("Have Error", error) return } guard let appGroup = appGroup else { return } self.groups.append(appGroup) } } disPatchGroup.notify(queue: DispatchQueue.main) { _ = self.groups.compactMap({$0}) self.collectionView.reloadData() } }
Brian Voong
4 years ago
ravibastola
4 years ago
What does compact map do? could you please tell?
seventhaxis
4 years ago
`compactMap` is a higher-order function that loops through each object, allowing you to manipulate the object and only returning it if it's not `nil`
qi
3 years ago
But if I'm not mistaken, this code can not assure the order of group inside appGroups?
Brian Voong
3 years ago
frankusu
4 years ago
Don't know if I'm using this right but can use DispatchSemaphores to keep the order fileprivate func fetchGroup() { let fetchDisPatchSemaphore = DispatchSemaphore(value: 0) for urlString in urlStrings { Service.shared.fetchAppGroup(url: urlString) { (appGroup, err) in fetchDisPatchSemaphore.signal() if let err = err { print("Failed to fetch games data", err) return } self.editorsGameGroup = appGroup self.appGroups.append(appGroup) } fetchDisPatchSemaphore.wait() DispatchQueue.main.async { self.collectionView.reloadData() } }
Brian Voong
4 years ago
frankusu
4 years ago
Ohh Brian, I think you're right it's not asynchronous, which defeats the whole purpose. I tried with Kritbovorn's way in the previous comment and can't seem to get the results to be in order. Do you have any suggestions that are different from in the video? Thanks!!
HELP & SUPPORT