How do I run a function with a parameter every 5 seconds in Swift?

2.9k views Asked by At

I want to execute the following function every 5 seconds. I have seen the other answers to related questions that say to use NSTimer.scheduledTimerWithTimeInterval but that only works for functions without any arguments.

The function:

func sayHello(name: String) {
    println("Hey \(name)!")
}

This is the code I tried:

var timer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: Selector("sayHello(\(name))"), userInfo: nil, repeats: true)

But it crashes the app with the error:

Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '-[Jake.SwiftApp sayHello(jake)]: unrecognized
selector sent to instance 0x7fcd515640a0'

How do I run the function with the one input parameter?

2

There are 2 answers

1
Jenner Felton On

I believe that you can have your function take the NSTimer as an argument, which then allows you to send your data in the userInfo like so.

var timer : NSTimer = NSTimer(timeInterval: 5.0, target: self, selector: Selector("sayHello:"), userInfo: "Jake", repeats: true)

func sayHello(timer: NSTimer) {
     println(timer.userInfo)
}

I haven't been able to test this so the timer might be malformed, but I think the logic is right.

3
nhgrif On

The problem is, when you set up the selector for the NSTimer to fire, you have to pass just the name of the method.

Right now, you're dynamically building a string and trying to point to a method called:

"sayHello(jake)"

Which doesn't exist. In fact, in can't exist. This doesn't even make sense.

The sayHello() fun would look like this if you're adding it as a selector with a string:

"sayHello:"

But just fixing this will still be problems. An NSTimer's target can take zero or one arguments. And if it does take an argument, it expects that argument to be an NSTimer. When NSTimer's fire and call their target, they pass themselves as a parameter.

So, we need to setup our method like this:

func sayHello(timer: NSTimer) {
    // do stuff
}

And now we pass Selector("sayHello:") for our selector argument.

In order to pass any sort of arguments in, we have to package them into the NSTimer's userInfo property, which is of type AnyObject?. Then, within the method our timer calls, we can access this property.

class ExampleClass {
    var timer: NSTimer?

    var userName: String? {
        set {
            self.timer?.userInfo = newValue
        }
        get {
            return self.timer?.userInfo as? String
        }
    }

    func timerTick(timer: NSTimer) {
        if let userName = timer.userInfo as? String {
            self.sayHello(userName)
        }
    }

    func sayHello(userName: String) {
        println("Hello " + userName + "!")
    }

    func startTimer(#interval: NSTimeInterval = 5.0, userName: String? = nil) {
        self.timer = NSTimer.scheduledTimerWithTimeInterval(interval, 
            target: self, 
            selector: Selector("timerTick:"), 
            userInfo: userName, 
            repeats: true
        )
    }
}

From here, any time you want to change the name the timer is carrying around, you can just do:

self.userName = "Jake"