-1

I have some code here:

let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
let types: [FeedTimeline] = [.special, .pr, .pickup, .lastPost]
var operations: [Operation] = []
for type in types {
            
    let operation = BlockOperation {
        print("Working")
        self.getArticles(of: type, page: 1, completion: { (articles) in
            print("Fetched")
        })
    }
    operation.completionBlock = {
        print("Done")
    }
    queue.addOperation(operation)

After I ran the code above, I get the result below:

Working

Done

Working

Working

Done

Done

Working

Done

After a while I got 4 times of "Fetched". Why is that? How to make completionBlock only run if the API request (getArticles) done.

What I want is: working -> fetched -> done -> working .... so on

Community
  • 1
  • 1
Tung Vu Duc
  • 1,552
  • 1
  • 13
  • 22

2 Answers2

3

The reason for this behaviour is that the operation continues executing (i.e. exits) once the call to self.getArticles has been executed, not after it has completed.

One way to fix this is to use dispatch groups. Try the following code:

    let queue = OperationQueue()
    let group = DispatchGroup()
    queue.maxConcurrentOperationCount = 1
    let types: [FeedTimeline] = [.special, .pr, .pickup, .lastPost]
    var operations: [Operation] = []

    for type in types {

        let operation = BlockOperation {
            group.enter()
            print("Working")
            self.getArticles(of: type, page: 1, completion: { (articles) in
                print("Fetched")
                group.leave()
            })
            group.wait(timeout: DispatchTime.distantFuture)
        }
        operation.completionBlock = {
            print("Done")
        }
        queue.addOperation(operation)

In this case the call to group.wait() will block until each call to group.enter() is matched by a call to group.leave(), meaning that the BlockOperation will wait until the call to getArticles has completed.

Note that you need to ensure that getArticles always executes the callback, if it doesn't (e.g. due to network timeout), the operation block will hang forever.

mlidal
  • 1,111
  • 14
  • 27
  • but this will block the current thread wont it? – Peter Lapisu Oct 26 '21 at 16:10
  • No, the group.wait command blocks the thread running the BlockOperation (preventing the operation to complete before the call to self.getArticles executes its completion-block) – mlidal Oct 28 '21 at 07:55
3

You need to use a subclass of Operation with overridden isExecuting and isFinished to tell the system when exactly your operation begins and when finishes. Something like this:

class AsyncOperation: Operation {
    override var isAsynchronous: Bool {
        return true
    }

    private let _queue = DispatchQueue(label: "asyncOperationQueue", attributes: .concurrent)
    private var _isExecuting: Bool = false

    override var isExecuting: Bool {
        set {
            willChangeValue(forKey: "isExecuting")
            _queue.async(flags: .barrier) {
                self._isExecuting = newValue
            }
            didChangeValue(forKey: "isExecuting")
        }

        get {
            return _isExecuting
        }
    }

    var _isFinished: Bool = false

    override var isFinished: Bool {
        set {
            willChangeValue(forKey: "isFinished")
            _queue.async(flags: .barrier) {
                self._isFinished = newValue
            }
            didChangeValue(forKey: "isFinished")
        }

        get {
            return _isFinished
        }
    }
}

Then subclass your operation from this new class. I guess reading this helps if you want to do it with operation and operation queue.

Maysam
  • 7,246
  • 13
  • 68
  • 106
  • 1
    Your asynchronous operation is not thread-safe. Please have a look at [this answer](https://stackoverflow.com/questions/43561169/trying-to-understand-asynchronous-operation-subclass/48104095#48104095) – vadian Aug 19 '19 at 07:14
  • It's better but the linked answer is a comprehensive and **complete** implementation. – vadian Aug 20 '19 at 14:21