How to pass callback functions in Swift

i0S Swift Issue

Question or problem with Swift language programming:

I have a simple class which init method takes an Int and a callback function.

class Timer {
  var timer = NSTimer()
  var handler: (Int) -> Void

  init(duration: Int, handler: (Int) -> Void) {
      self.duration = duration
      self.handler = handler
      self.start()
  }
  @objc func someMethod() {        
      self.handler(10)
  }
}

Then in the ViewController I have this:

var timer = Timer(duration: 5, handler: displayTimeRemaining)
func displayTimeRemaining(counter: Int) -> Void {
    println(counter)
}

This doesn’t work, I get the following:

‘Int’ is not a subtype of ‘SecondViewController’

Edit 1: Adding full code.

Timer.swift

import UIKit

class Timer {
    lazy var timer = NSTimer()
    var handler: (Int) -> Void

    let duration: Int
    var elapsedTime: Int = 0

    init(duration: Int, handler: (Int) -> Void) {
        self.duration = duration
        self.handler = handler
        self.start()
    }

    func start() {
        self.timer = NSTimer.scheduledTimerWithTimeInterval(1.0,
            target: self,
            selector: Selector("tick"),
            userInfo: nil,
            repeats: true)
    }

    func stop() {
        timer.invalidate()
    }

    func tick() {
        self.elapsedTime++

        self.handler(10)

        if self.elapsedTime == self.duration {
            self.stop()
        }
    }

    deinit {
        self.timer.invalidate()
    }
}

SecondViewController.swift

import UIKit

class SecondViewController: UIViewController {

    @IBOutlet var cycleCounter: UILabel!
    var number = 0

    var timer = Timer(duration: 5, handler: displayTimeRemaining)

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func btnIncrementCycle_Click(sender: UIButton){
        cycleCounter.text = String(++number)
        println(number)
    }

    func displayTimeRemaining(counter: Int) -> Void {
        println(counter)
    }
}

I just started with Swift so I’m very green. How are you supposed to pass callbacks? I’ve looked at examples and this should be working I think. Is my class defined incorrectly for the way I’m passing the callback?

Thanks

How to solve the problem:

Solution 1:

Ok, now with the full code I was able to replicate your issue. I’m not 100% sure what the cause is but I believe it has something to do with referencing a class method (displayTimeRemaining) before the class was instantiated. Here are a couple of ways around this:

Option 1: Declare the handler method outside of the SecondViewController class:

func displayTimeRemaining(counter: Int) -> Void {
  println(counter)
}


class SecondViewController: UIViewController {    

  // ...
  var timer = Timer(duration: 5, handler: displayTimeRemaining)

Option 2: Make displayTimeRemaining into a type method by adding the class keyword to function declaration.

class SecondViewController: UIViewController {

  var timer: Timer = Timer(duration: 5, handler: SecondViewController.displayTimeRemaining)

  class func displayTimeRemaining(counter: Int) -> Void {
    println(counter)
  }

Option 3: I believe this will be the most inline with Swift’s way of thinking – use a closure:

class SecondViewController: UIViewController {

var timer: Timer = Timer(duration: 5) {
        println($0) //using Swift's anonymous method params
    }

Solution 2:

Simplest way

override func viewDidLoad() {
        super.viewDidLoad()

        testFunc(index: 2, callback: { str in
            print(str)
        })

    }


    func testFunc(index index: Int, callback: (String) -> Void) {
        callback("The number is \(index)")
    }

Solution 3:

Your problem is in the line:

var timer = NSTimer()

You cannot instantiate an NSTimer in this way. It has class methods that generate an object for you. The easiest way to get around the problem in this case is to make the definition lazy, like so:

lazy var timer = NSTimer()

In this way the value for timer won’t actually be touched until the start method which sets it up properly. NOTE: There is probably a safer way to do this.

Hope this helps!