I'm downloading some JSON from an API, but there is another prerequisite (an animation) that must be complete before we move to the next screen.
I've decided to use DispatchGroup to do this, but also need to have a reload as well.
I've come up with a solution that uses a completion handler that is either dispatchGroup.leave or simply moving to the next screen, since we only need to download the data once.
let dispatchGroup = DispatchGroup()
var userName: String?
func fetchData() {
dispatchGroup.enter()
dispatchGroup.enter()
resolveDataRequirements(dispatchGroup.leave)
dispatchGroup.notify(queue: .main) { [weak self] in
self?.moveToNextScreen()
}
}
func resolveDataRequirements(_ completion: #escaping(() -> Void)) {
api.resolve { [weak self] result in
switch result {
case .success(let user):
self?.userName = user
completion()
case .failure:
break
}
}
}
func moveToNextScreen() {
// move to next screen, but passes the user
}
// reload data, and once the data is downloaded move to next screen
func reloadData() {
resolveDataRequirements(moveToNextScreen)
}
// the user might choose to fetch data
fetchData()
// now if the user wanted to reload
reloadData()
Now the problem is that this is in a view model - so the user String is effectively a state that I wish to eradicate.
Is there any way I can pass the user String to dispatchGroup.notify or similar?
You can use inout properties and change userName scope like this:
func fetchData() {
var userName = ""
dispatchGroup.enter()
dispatchGroup.enter()
resolveDataRequirements(dispatchGroup.leave, &userName)
dispatchGroup.notify(queue: .main) { [weak self] in
self?.moveToNextScreen(userName)
}
}
func resolveDataRequirements(_ completion: #escaping(() -> Void), _ userName: inout String) {
api.resolve { [weak self] result in
switch result {
case .success(let user):
userName = user
completion()
case .failure:
break
}
}
}
func moveToNextScreen(_ userName: String) {
print(userName)
}
I just don't understand why you are calling enter() twice and leave() just once
Related
My current program is about mostly TableViews and CollectionViews that fetches data from an API, The problem is that the data is fetched only when the user goes to the current tab which the TableView is present and the data fetches from the moment the user went to the tab and needs to wait till the data come because when user logs-in a token is shared with other classes and used later to fill them.
I don't know how can I make the data filled automatically after the user is logged-in not when the user goes to the specific tab and wait for them to load, Basically make a request when the user loggs-in so he doesn't have to go to the tabs and wait 5+ seconds
signInViewController:
#IBAction func signInSegueToDashboard(_ sender: Any) {
activityLoaderSignIn.startAnimating()
APICallerPOST.shared.signInToAccount(username: emailFieldSignIn.text!, password: passwordFieldSignIn.text!) { [self] (result, error) in
if result?.StatusCode != 0 {
DispatchQueue.main.async {
activityLoaderSignIn.stopAnimating()
}
}
switch result?.StatusCode {
case 0:
APICallerGET.shared.token = result?.Result.token
assignDataFromSignIn.DefaultsKeys.keyOne = (result?.Result.completeName)!
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
guard let mainTabBarController = self.storyboard?.instantiateViewController(withIdentifier: "mainTabBarController")
else {
return
}
mainTabBarController.modalPresentationStyle = .custom
self.present(mainTabBarController, animated: true, completion: nil)
}
case 1:
DispatchQueue.main.async {
}
case 2:
DispatchQueue.main.async {
}
default:
break
}
}
}
invoicesViewController:
// the function which is used at viewDidLoad at a TableView class
func fetchAndReloadData(){
APICallerGET.shared.propertiesOfAccount(for: APICallerGET.shared.token!) { [self] (result, error) in
switch result?.StatusCode {
case 0:
self.notificationsNew = result!
self.notifications = self.notificationsNew
DispatchQueue.main.async {
self.invoicesTableView.reloadData()
activityLoaderInvoices.stopAnimating()
}
case 1:
print("error")
default:
break
}
}
}
Now if there is a way to load this function of the invoicesViewController straight when the user signs-in so he doesn't have to wait till the data loads when he went to the tab with the TableView.
I am writing the iOS application using swift 4.2. I am making a service call to logout user.
I need to know where to use main thread (DispatchQueue.main.async).
Here is my code:
private func handleLogoutCellTap() {
logoutUseCase?.logout() { [weak self] (result) in
guard let self = self else { return }
switch result {
case let (.success(didLogout)):
didLogout ? self.handleSuccessfullLogout() : self.handleLogoutError(with: nil)
case let (.failure(error)):
self.handleLogoutError(with: error)
}
}
}
logoutUseCase?.logout() makes a service call and returns #escaping completion. Should I use DispatchQueue.main.async on this whole handleLogoutCellTap() function or just in a handling segment?
Move the control to main thread wherever you're updating the UI after receiving the response of logout.
If handleSuccessfullLogout() and handleLogoutError(with:) methods perform any UI operation, you can embed the whole switch statement in DispatchQueue.main.async, i,e.
private func handleLogoutCellTap() {
logoutUseCase?.logout() { [weak self] (result) in
guard let self = self else { return }
DispatchQueue.main.async { //here.....
switch result {
//rest of the code....
}
}
}
}
I need to perform an async operation for each element in an array, one at at time. This operation calls back on the main queue.
func fetchResults(for: array, completion: () -> Void) {
var results: [OtherObject]: []
let queue = DispatchQueue(label: "Serial Queue")
queue.sync {
let group = DispatchGroup()
for object in array {
group.enter()
WebService().fetch(for: object) { result in
// Calls back on main queue
// Handle result
results.append(something)
group.leave()
}
group.wait()
}
}
print(results) // Never reached
completion()
}
The WebService call isn't calling back - which I think is telling me the main queue is blocked, but I can't understand why.
You should use group.notify() rather than group.wait(), since the latter is a synchronous, blocking operation.
I also don't see a point of dispatching to a queue if you only dispatch a single work item once.
func fetchResults(for: array, completion: () -> Void) {
var results: [OtherObject]: []
let group = DispatchGroup()
for object in array {
group.enter()
WebService().fetch(for: object) { result in
// Calls back on main queue
// Handle result
results.append(something)
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
print(results)
completion()
}
}
Maybe it's just a typo but basically don't run the queue synchronously.
Then instead of wait use notify outside(!) of the loop and print the results within the queue.
queue.async {
let group = DispatchGroup()
for object in array {
group.enter()
WebService().fetch(for: object) { result in
// Calls back on main queue
// Handle result
results.append(something)
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
print(results)
completion()
}
}
I d'ont think your main queue is locked, otherwise you would probably have an infinite loading on your app, as if it crashed ( in MacOS that's for sure ).
Here is what worked for me, maybe it will help :
class func synchronize(completion: #escaping (_ error: Bool) -> Void) {
DispatchQueue.global(qos: .background).async {
// Background Thread
var error = false
let group = DispatchGroup()
synchronizeObject1(group: group){ error = true }
synchronizeObject2(group: group){ error = true }
synchronizeObject3(group: group){ error = true }
group.wait() // will wait for everyone to sync
DispatchQueue.main.async {
// Run UI Updates or call completion block
completion(error)
}
}
}
class func synchronizeObject1(group: DispatchGroup, errorHandler: #escaping () -> Void){
group.enter()
WebservicesController.shared.getAllObjects1() { _ in
// Do My stuff
// Note: if an error occures I call errorHandler()
group.leave()
}
}
If I would say, it may come from the queue.sync instead of queue.async. But I'm not an expert on Asynchronous calls.
Hope it helps
I'm using DispatchGroup.enter() and leave() to process a helper class's reverseG async function. Problem is clear, I'm using mainViewController's object to call mainViewControllers's dispatchGroup.leave() in helper class! Is there a way to do it?
Same code works when reverseG is declared in the main view controller.
class Geo {
var obj = ViewController()
static func reverseG(_ coordinates: CLLocation, _ completion: #escaping (CLPlacemark) -> ()) {
let geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(coordinates) { (placemarks, error) in
if let error = error {
print("error: \(error.localizedDescription)")
}
if let placemarks = placemarks, placemarks.count > 0 {
let placemark = placemarks.first!
completion(placemark) // set ViewController's properties
} else {
print("no data")
}
obj.dispatchGroup.leave() // ** ERROR **
}
}
}
Function call from main view controller
dispatchGroup.enter()
Geo.reverseG(coordinates, setValues) // completionHandler: setValues
dispatchGroup.notify(queue: DispatchQueue.main) {
// call another function on completion
}
Every leave call must have an associated enter call. If you call leave without having first called enter, it will crash. The issue here is that you're calling enter on some group, but reverseG is calling leave on some other instance of ViewController. I'd suggest passing the DispatchGroup as a parameter to your reverseG method. Or, better, reverseG shouldn't leave the group, but rather put the leave call inside the completion handler that reserveG calls.
dispatchGroup.enter()
Geo.reverseG(coordinates) { placemark in
defer { dispatchGroup.leave() }
guard let placemark = placemark else { return }
// use placemark here, e.g. call `setValues` or whatever
}
dispatchGroup.notify(queue: DispatchQueue.main) {
// call another function on completion
}
And
class Geo {
// var obj = ViewController()
static func reverseG(_ coordinates: CLLocation, completion: #escaping (CLPlacemark?) -> Void) {
let geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(coordinates) { placemarks, error in
if let error = error {
print("error: \(error.localizedDescription)")
}
completion(placemarks?.first)
// obj.dispatchGroup.leave() // ** ERROR **
}
}
}
This keeps the DispatchGroup logic at one level of the app, keeping your classes less tightly coupled (e.g. the Geo coder doesn't need to know whether the view controller uses dispatch groups or not).
Frankly, I'm not clear why you're using dispatch group at all if there's only one call. Usually you'd put whatever you call inside the completion handler, simplifying the code further. You generally only use groups if you're doing a whole series of calls. (Perhaps you've just simplified your code snippet whereas you're really doing multiple calls. In that case, a dispatch group might make sense. But then again, you shouldn't be doing concurrent geocode requests, suggesting a completely different pattern, altogether.
Passed dispatchGroup as parameter with function call and it worked.
Geo.reverseG(coordinates, dispatchGroup, setValues)
my two cents to show how can work:
(maybe useful for others..)
// Created by ing.conti on 02/02/21.
//
import Foundation
print("Hello, World!")
let r = AsyncRunner()
r.runMultiple(args: ["Sam", "Sarah", "Tom"])
class AsyncRunner{
static let shared = AsyncRunner()
let dispatchQueue = DispatchQueue(label: "MyQueue", qos:.userInitiated)
let dispatchGroup = DispatchGroup.init()
func runMultiple(args: [String]){
let count = args.count
for i in 0..<count {
dispatchQueue.async(group: dispatchGroup) { [unowned self] in
dispatchGroup.enter()
self.fakeTask(arg: args[i])
}
}
_ = dispatchGroup.wait(timeout: DispatchTime.distantFuture)
}
func fakeTask(arg: String){
for i in 0..<3 {
print(arg, i)
sleep(1)
}
dispatchGroup.leave()
}
}
After reading Swift 3 evolution on GCD, I am trying to create dispatch group. The problem is the group.notify(queue: do not notify when I pass DispatchQueue.main as a queue, although it does work for background queue.
Also I am not sure my syntax is all correct, as I am trying to convert code from Swift 2 to Swift 3.
typealias CallBack = (result: Bool) -> Void
func longCalculations (completion: CallBack) {
let backgroundQ = DispatchQueue.global(attributes: .qosBackground)
let group = DispatchGroup()
var fill:[Int] = []
for item in 0...200 {
group.enter()
if item > 50 {
fill.append(item)
}
group.leave()
}
//Below in the notify argument If I pass `backgroundQ`, it seems to work correctly but not when DispatchQueue.main is passed.
This code do not work
group.notify(queue: DispatchQueue.main, execute: {
completion(result: true)
})
}
This works correctly
group.notify(queue: backgroundQ, execute: {
completion(result: true)
})
}
_______________________________________________________
longCalculations() { print($0) }
After reading post suggested by Matt, I found that I was submitting task to main queue and when I asked to be notified on main thread itself, it got in the deadlock.
I have altered the code and now it is working as intended,
typealias CallBack = (result: [Int]) -> Void
func longCalculations (completion: CallBack) {
let backgroundQ = DispatchQueue.global(attributes: .qosDefault)
let group = DispatchGroup()
var fill:[Int] = []
for number in 0..<100 {
group.enter()
backgroundQ.async(group: group, execute: {
if number > 50 {
fill.append(number)
}
group.leave()
})
}
group.notify(queue: DispatchQueue.main, execute: {
print("All Done"); completion(result: fill)
})
}
longCalculations(){print($0)}