2

I am working on iOS app where I am using OperationQueue. I have created 2 operations. Operation2 is dependent on completion of Operation1.Operation2 needs to wait until Operation 1 get finished if its running. If operation 1 is not running then operation 2 should start immediately.

Its not working as expected so I am testing in playground

    class MyManager {

        var operationQueue: OperationQueue?

        var operation1: MyOperation? = nil
        var operation2: MyOperation? = nil

        typealias completion = (_ serverError: String?) -> Void

        func talkWithServer(completion: completion?) {

            completion?("competed!")
        }

        func doOperation1() {

            cancelProcess()
            setup()

            guard let operation1 = self.operation1 else { return }
            operation1.codeToRun = {
                print("operation1 started")
                self.talkWithServer(completion: { (completion) in
                    print("operation1 completed")
                    operation1.markAsFinished()
                })
            }
            operationQueue?.addOperation(operation1)

        }


        func doOperation2() {
           self.operation2 = MyOperation()

            guard let operation2 = self.operation2 else { return }



            operation2.codeToRun = {
                print("operation2 started")
                self.talkWithServer(completion: { (completion) in
                    print("operation2 completed")
                    operation2.markAsFinished()
                })
            }

if let operation1 = self.operation1 {
            if operation1.isExecuting {
                operation2.addDependency(operation1)
                operation1.completionBlock = {
                    print("operation1.completionBlock")
                    self.operationQueue?.addOperation(operation2)
                }
            }
            } else  {
                operationQueue?.addOperation(operation2)
            }

        }


        func cancelProcess() {
            print("cancelAllOperations")
            operationQueue?.cancelAllOperations()
        }

        func setup() {
            print("setup Called")
            operationQueue?.cancelAllOperations()
            operationQueue = OperationQueue()
            operation1 = MyOperation()
            operation2 = MyOperation()
        }
    }


    class MyOperation: Operation {
        var codeToRun: (()->Void)?

        var _executing = false
        var _finished = false

        override internal(set) var isExecuting: Bool {
            get {
                return _executing
            }
            set {
                _executing = newValue

            }
        }

        override internal(set) var isFinished: Bool {
            get {
                return _finished
            }
            set {
                _finished = newValue
            }
        }

        override var isAsynchronous: Bool {
            return true
        }

        override func start() {
            isExecuting = true
            isFinished = false
            if let closure = self.codeToRun {
                closure()
            }
        }

        func markAsFinished() {
            self.isExecuting = false
            self.isFinished = true
            completionBlock?()
        }
    }

    let manager = MyManager()

    manager.doOperation1()
    manager.doOperation2()

I am getting result

cancelAllOperations
setup Called
operation1 started
operation1 completed
operation1.completionBlock

Expected is

cancelAllOperations
setup Called
operation1 started
operation1 completed
operation1.completionBlock
operation2 started
operation2 completed

Am I missing here anything?

Kapil
  • 133
  • 2
  • 11
  • Not sure I understand your question. You have written in the 2nd line of your question that operation2 can start after operation1 is in progress or completed. Is this a typo because if you add dependency it will wait till operation1 completes. Why are you setting the `codeToRun` after adding it to the queue. It needs to be done before adding it to the queue. – user1046037 Feb 03 '18 at 04:42
  • Is this implementation of your operation copied from somewhere ? There are a number of issues with your code. Could you please the read the documentation of Operation. – user1046037 Feb 03 '18 at 04:48
  • codeToRun is set before adding to queue. Corrected. – Kapil Feb 03 '18 at 05:01
  • Do you want the 2nd operation to start after the 1st operation completes ? If so could you edit your 2nd line of your question ? – user1046037 Feb 03 '18 at 05:03
  • Corrected Thanks – Kapil Feb 03 '18 at 05:08
  • Two ways to look into achieving this are to add operation1 as a dependency of operation2 but of course it you never have an operation1 for some reason that fails. The other method is to set the `maxConcurrentOperationCount` property of the operation queue to 1 so that only 1 operation can execute at any time. Then if operation1 starts first operation2 has to wait for it but if there is no operation1 operation 2 will just start. Doing this the operations will execute in the order they are added and each one waits for the previous one to finish. – Upholder Of Truth Feb 03 '18 at 08:08

3 Answers3

1

I've been looking at your code. I found a few things:

First

manager.doOperation1()
manager.doOperation2()

this not means operation2 runs after operation1 finished, if you want to do this, you can add a completion closure for operation1.

Second

when you call

doOperation2()

in this function seems like the code never executed after:

guard let operation2 = self.operation2 else { return }

After all

It seems like you want to create your own wheels. I suggest you to learn something about GCD,you can find resources here:

Grand Central Dispatch Crash Course for Swift 3

Grand Central Dispatch Tutorial for Swift 3: Part 1/2

Mo Abdul-Hameed
  • 6,030
  • 2
  • 23
  • 36
  • Thanks for documentation. I would like to use OperationQueue instead of GCD. I am trying to figure out why dependency is not working – Kapil Feb 03 '18 at 03:43
0

There are a couple of things:

Implementation:

  • Implement KVO for isExecuting and isFinished
  • Fix doOperation2
  • Once you set the dependencies the operation2 wouldn't start even if it is added to the queue until operation1 completes.
  • Check for isCancelled inside MyOperation

Below is not the ideal way to implement doOperation2, but removes some of the clutter from your code. I will leave it to you to implement the whole thing based on the Design section mentioned below.

func doOperation2() {

    self.operation2 = MyOperation()

    guard let operation2 = self.operation2 else {
        return
    }

    operation2.codeToRun = {
        print("operation2 started")

        self.talkWithServer(completion: { (completion) in
            print("operation2 completed")
        })
    }

    operationQueue?.addOperation(operation2)
}

Design:

  • In your implementation, MyOperation seems to be generic and you seem to be doing most of the real work at the place where you call them
  • Modify MyOperation to do the real work.
  • Call site should be simple
  • Example FetchData() is an operation, ParseData() is another operation.
  • So an operation contains business logic.
  • At the call site you can just add dependencies.
user1046037
  • 16,755
  • 12
  • 92
  • 138
0

All you need to do is just add dependency on the dependent operation.

let queue = OperationQueue()

let operation1 = BlockOperation(block: { [weak self] in
    self?.doOperation1()
})

let operation2 = BlockOperation(block: { [weak self] in
    self?.doOperation2()
})

operation1.addDependency(operation2) // THIS IS THE KEY CODE IN YOUR CASE

queue.addOperation(operation1)
queue.addOperation(operation2)

Hope this may help you solve your dependency problem.

shim
  • 9,289
  • 12
  • 69
  • 108
Dhaval H. Nena
  • 3,992
  • 1
  • 37
  • 50