1

I have this code set up in a subclass of an Operation in Swift. But I can't figure out why the timer is only calling the selector once.

class DogOperationRun : DogOperation {

let thisDog:Dog?
let operationID:Int?
var runDistance:Double?
var dogSpeed:Double?

var runTimeLeft:Double = 0.0
var currentRunDistance:Double = 0.0
private var interval = 0.0
private var cycle = 0.0
private var dogTimer:Timer?

init(thisDog:Dog, operationID:Int, runDistance:Double, dogSpeed:Double) {

    self.thisDog = thisDog
    self.operationID = operationID
    self.runDistance = runDistance
    self.dogSpeed = dogSpeed

    super.init(self.operationID!)
}

override func main() {
    guard isCancelled == false else {
        operationIsFinished = true
        return
    }

    startRun()
}

func startRun() {

    operationIsExecuting = true

    if let distance = runDistance, let thisDogSpeed = dogSpeed {
        runTimeLeft = distance
        interval = distance/thisDogSpeed
        dogTimer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(runDog), userInfo: nil, repeats: true)
    }

}

@objc func runDog() {
    runTimeLeft -= interval
    cycle += 1

    if runTimeLeft > 0 {
        runDistance = interval * cycle
        print("\(self.thisDog!.name) has run \(runDistance!) meters!")
    } else {
        dogTimer?.invalidate()
        operationIsFinished = true
    }

}

}

and the superclass

import Foundation

class DogOperation: Operation {

public var id:Int


//going to over ride the existing variable and return my private one
override var isExecuting: Bool {
    return operationIsExecuting
}

var operationIsExecuting = false {

    didSet {

        willChangeValue(forKey: "isExecuting")
        print("Operation \(id) will execute")

        didChangeValue(forKey: "isExecuting")
        print("Operation \(id) is executing")
    }
}

override var isFinished: Bool {
    return operationIsFinished
}

var operationIsFinished = false {

    didSet {

        willChangeValue(forKey: "isFinished")
        print("Operation \(id) will finish")

        didChangeValue(forKey: "isFinished")
        print("Operation \(id) is finished")
    }
}


init(_ id:Int) {
    self.id = id
}
}

calling it like so:

let lodiDogRun = DogOperationRun(thisDog: aDog, operationID: 1, runDistance: 100.0, dogSpeed: 12.0)
    let pretzelDogRun = DogOperationRun(thisDog: nextDog, operationID: 2, runDistance: 100.0, dogSpeed: 14.0)

    myOperationQueue.addOperations([lodiDogRun, pretzelDogRun], waitUntilFinished: true)

Edit for Matt:The only time dogRun gets called is when I force the timer with dogTimer.fire(). For the instance I captured it on it was running on thread 5:

enter image description here

PruitIgoe
  • 6,166
  • 16
  • 70
  • 137
  • what are your values for runDistance and dogSpeed ? – Jan Misker Apr 15 '18 at 16:15
  • hi Jan, I edited the code to include the full class and superclass – PruitIgoe Apr 15 '18 at 16:32
  • when `runDog` is called, what does logging / debugging show about which branch it takes? – matt Apr 15 '18 at 16:40
  • @matt - see my edits – PruitIgoe Apr 15 '18 at 16:46
  • OK so the timer isn't running _at all_? That's very different from what you asked. It also doesn't entirely surprise me: a timer on a background thread is a very tricky thing – matt Apr 15 '18 at 16:49
  • yeah, my bad, I didn't realize the Timer was not firing at all at first. – PruitIgoe Apr 15 '18 at 16:50
  • No problem. It's pretty simple: you cannot just `schedule` the timer, you have to give it a runloop. – matt Apr 15 '18 at 16:51
  • Yeah, I'm reading through the linked duplicate question now, seems straight forward enough to resolve. Make your comment an answer and I'll give you a bump. – PruitIgoe Apr 15 '18 at 16:52
  • Your question is a duplicate, I would suggest just deleting it. Glad to have gotten you pointed in the right direction. – matt Apr 15 '18 at 16:58
  • FWIW, two observations: First, creating run loop for timer is "old school" and inefficient; GCD timers are much better way to do that. Second, your KVO for your asynchronous `Operation` implementation, above, is not correct. You should do `willChangeValue` notification inside `willSet` and `didChangeValue` inside `didSet`. You also should make these thread-safe properties. E.g. `AsynchronousOperation` in https://stackoverflow.com/a/48104095/1271826 or https://stackoverflow.com/a/27022598/1271826. – Rob Apr 15 '18 at 17:22
  • BTW, I notice you use `addOperations` with `waitUntilFinished` set to `true`. Clearly, you'd avoid ever waiting. Why go through this exercise of timers and asynchronous operations only to block the calling thread. Lol. – Rob Apr 15 '18 at 18:01

0 Answers0