0

I am trying to exit from for loop according to condition but I ran into the issue as it does not exit even from the loop. Here is a loop of my code.

var isFailure = true
let dispatchGroup = DispatchGroup()
var myFailureTask: Int?
for item in 1...5 {
  dispatchGroup.enter()
  test(item: item, completion: {
    print("Success\(item)")
    dispatchGroup.leave()
  }, failureBlock: {
    print("Failure\(item)")
    myFailureTask = item
    dispatchGroup.leave()
    return
  })
  dispatchGroup.wait()
}
dispatchGroup.notify(queue: .main) {
  if let myFailure = myFailureTask {
    print("task failure \(myFailure)")
  } else {
    print("all task done")
  }
}
func test(item: Int,completion: @escaping(() -> ()), failureBlock: @escaping(() -> ())) {
    Thread.sleep(forTimeInterval: TimeInterval(item))
  isFailure = !isFailure
  if isFailure {
    failureBlock()
  } else {
    completion()
  }
}
Prashant Ghimire
  • 518
  • 4
  • 20
  • You need to have a `break` somewhere within the loop to exit it, however the `break` needs to be placed in the synchronous part of the loop. BTW, why do you need to break the loop, what the problem you're trying to solve? – Cristik Jun 02 '21 at 04:44
  • I need to exit from the loop when failure of one item in the loop as API call depends upon the success of the previous item in the loop. – Prashant Ghimire Jun 02 '21 at 05:23
  • 1
    Then this - https://stackoverflow.com/questions/51004245/synchronise-multiple-web-service-calls-in-serial-order-in-swift - should help you. – Cristik Jun 02 '21 at 05:29
  • I just made few changes and work like charm. thanks @Cristik – Prashant Ghimire Jun 02 '21 at 06:41

2 Answers2

1

The return returns from current scope.

In this case it's returning from the failureBlock: {} and NOT from the for loop scope.

You have to refactor the code to achieve what you are trying to do.

EITHER (in case this code is executing synchronously) you can return a success value true | false from the function by making function's return type Bool and removing the failureBlock argument.

OR (in case this code is executing asynchronously) you have to think of waiting on one task to complete/fail before triggering the other.

UPDATE

I think following might be a simplified version of this code -

var isFailure: Bool = false

func callTest(for item: Int) {
    print("task initiated \(item)")
    test(item: item, completion: {
        print("task succeeded \(item)")
        if item < 5 {
            callTest(for: item+1)
        } else {
            print("all tasks done")
        }
    }, failureBlock: {
        print("task failed \(item)")
    })
}

func test(item: Int, completion: @escaping (() -> Void), failureBlock: @escaping (() -> Void)) {
    Thread.sleep(forTimeInterval: TimeInterval(item))
    isFailure.toggle()
    if isFailure {
        failureBlock()
    } else {
        completion()
    }
}

callTest(for: 1)
Tarun Tyagi
  • 9,364
  • 2
  • 17
  • 30
  • Thanks. In my test function, there will be an asynchronous task, ie API call, the second task will depend upon the previous task. Is there any way to return from the loop? – Prashant Ghimire Jun 02 '21 at 05:28
0
var isFailure = false
let dispatchGroup = DispatchGroup()
var myFailureTask: Int?
for item in 1...5 {
  dispatchGroup.enter()
  test(item: item, completion: {
    print("Success\(item)")
    dispatchGroup.leave()
  }, failureBlock: {
    print("Failure\(item)")
    myFailureTask = item
    dispatchGroup.leave()
  })
  if isFailure == true {
    break
  }
  dispatchGroup.wait()
}
dispatchGroup.notify(queue: .main) {
  if let myFailure = myFailureTask {
    print("task failure \(myFailure)")
  } else {
    print("all task done")
  }
}
func test(item: Int,completion: @escaping(() -> ()), failureBlock: @escaping(() -> ())) {
    Thread.sleep(forTimeInterval: TimeInterval(item))
  isFailure = !isFailure
  if isFailure {
    failureBlock()
  } else {
    completion()
  }
}```
Made few changes work like a charm.
Any one has better idea please comment
Prashant Ghimire
  • 518
  • 4
  • 20