How Can I Unit Test Swift Timer Controller? - swift

I am working a project that will utilize Swift's Timer class. My TimerController class will control a Timer instance by starting, pausing, resuming, and resetting it.
TimerController consists of the following code:
internal final class TimerController {
// MARK: - Properties
private var timer = Timer()
private let timerIntervalInSeconds = TimeInterval(1)
internal private(set) var durationInSeconds: TimeInterval
// MARK: - Initialization
internal init(seconds: Double) {
durationInSeconds = TimeInterval(seconds)
}
// MARK: - Timer Control
// Starts and resumes the timer
internal func startTimer() {
timer = Timer.scheduledTimer(timeInterval: timerIntervalInSeconds, target: self, selector: #selector(handleTimerFire), userInfo: nil, repeats: true)
}
internal func pauseTimer() {
invalidateTimer()
}
internal func resetTimer() {
invalidateTimer()
durationInSeconds = 0
}
// MARK: - Helpers
#objc private func handleTimerFire() {
durationInSeconds += 1
}
private func invalidateTimer() {
timer.invalidate()
}
}
Currently, my TimerControllerTests contains the following code:
class TimerControllerTests: XCTestCase {
func test_TimerController_DurationInSeconds_IsSet() {
let expected: TimeInterval = 60
let controller = TimerController(seconds: 60)
XCTAssertEqual(controller.durationInSeconds, expected, "'durationInSeconds' is not set to correct value.")
}
}
I am able to test that the timer's expected duration is set correctly when initializing an instance of TimerController. However, I don't know where to start testing the rest of TimerController.
I want to ensure that the class successfully handles startTimer(), pauseTimer(), and resetTimer(). I want my unit tests to run as quickly as possible, but I think that I need to actually start, pause, and stop the timer to test that the durationInSeconds property is updated after the appropriate methods are called.
Is it appropriate to actually create the timer in TimerController and call the methods in my unit tests to verify that durationInSeconds has been updated correctly?
I realize that it will slow my unit tests down, but I don't know of another way to appropriately test this class and it's intended actions.
Update
I have been doing some research, and I have found, what I think to be, a solution that seems to get the job done as far as my testing goes. However, I am unsure whether this implementation is sufficient.
I have reimplemented my TimerController as follows:
internal final class TimerController {
// MARK: - Properties
private var timer = Timer()
private let timerIntervalInSeconds = TimeInterval(1)
internal private(set) var durationInSeconds: TimeInterval
internal var isTimerValid: Bool {
return timer.isValid
}
// MARK: - Initialization
internal init(seconds: Double) {
durationInSeconds = TimeInterval(seconds)
}
// MARK: - Timer Control
internal func startTimer(fireCompletion: (() -> Void)?) {
timer = Timer.scheduledTimer(withTimeInterval: timerIntervalInSeconds, repeats: true, block: { [unowned self] _ in
self.durationInSeconds -= 1
fireCompletion?()
})
}
internal func pauseTimer() {
invalidateTimer()
}
internal func resetTimer() {
invalidateTimer()
durationInSeconds = 0
}
// MARK: - Helpers
private func invalidateTimer() {
timer.invalidate()
}
}
Also, my test file has passing tests:
class TimerControllerTests: XCTestCase {
// MARK: - Properties
var timerController: TimerController!
// MARK: - Setup
override func setUp() {
timerController = TimerController(seconds: 1)
}
// MARK: - Teardown
override func tearDown() {
timerController.resetTimer()
super.tearDown()
}
// MARK: - Time
func test_TimerController_DurationInSeconds_IsSet() {
let expected: TimeInterval = 60
let timerController = TimerController(seconds: 60)
XCTAssertEqual(timerController.durationInSeconds, expected, "'durationInSeconds' is not set to correct value.")
}
func test_TimerController_DurationInSeconds_IsZeroAfterTimerIsFinished() {
let numberOfSeconds: TimeInterval = 1
let durationExpectation = expectation(description: "durationExpectation")
timerController = TimerController(seconds: numberOfSeconds)
timerController.startTimer(fireCompletion: nil)
DispatchQueue.main.asyncAfter(deadline: .now() + numberOfSeconds, execute: {
durationExpectation.fulfill()
XCTAssertEqual(0, self.timerController.durationInSeconds, "'durationInSeconds' is not set to correct value.")
})
waitForExpectations(timeout: numberOfSeconds + 1, handler: nil)
}
// MARK: - Timer State
func test_TimerController_TimerIsValidAfterTimerStarts() {
let timerValidityExpectation = expectation(description: "timerValidity")
timerController.startTimer {
timerValidityExpectation.fulfill()
XCTAssertTrue(self.timerController.isTimerValid, "Timer is invalid.")
}
waitForExpectations(timeout: 5, handler: nil)
}
func test_TimerController_TimerIsInvalidAfterTimerIsPaused() {
let timerValidityExpectation = expectation(description: "timerValidity")
timerController.startTimer {
self.timerController.pauseTimer()
timerValidityExpectation.fulfill()
XCTAssertFalse(self.timerController.isTimerValid, "Timer is valid")
}
waitForExpectations(timeout: 5, handler: nil)
}
func test_TimerController_TimerIsInvalidAfterTimerIsReset() {
let timerValidityExpectation = expectation(description: "timerValidity")
timerController.startTimer {
self.timerController.resetTimer()
timerValidityExpectation.fulfill()
XCTAssertFalse(self.timerController.isTimerValid, "Timer is valid")
}
waitForExpectations(timeout: 5, handler: nil)
}
}
The only thing that I can think of to make the tests faster is for me to mock the class and change let timerIntervalInSeconds = TimeInterval(1) to private let timerIntervalInSeconds = TimeInterval(0.1).
Is it overkill to mock the class so that I can use a smaller time interval for testing?

Rather than use a real timer (which would be slow), we can verify calls to a test double.
The challenge is that the code calls a factory method, Timer.scheduledTimer(…). This locks down a dependency. Testing would be easier if the test could provide a mock timer instead.
Usually, a good way to inject a factory is by supplying a closure. We can do this in the initializer, and provide a default value. Then the closure, by default, will make the actual call to the factory method.
In this case, it's a little complicated because the call to Timer.scheduledTimer(…) itself takes a closure:
internal init(seconds: Double,
makeRepeatingTimer: #escaping (TimeInterval, #escaping (TimerProtocol) -> Void) -> TimerProtocol = {
return Timer.scheduledTimer(withTimeInterval: $0, repeats: true, block: $1)
}) {
durationInSeconds = TimeInterval(seconds)
self.makeRepeatingTimer = makeRepeatingTimer
}
Note that I removed all references to Timer except inside the block. Everywhere else uses a newly-defined TimerProtocol.
self.makeRepeatingTimer is a closure property. Call it from startTimer(…).
Now test code can supply a different closure:
class TimerControllerTests: XCTestCase {
var makeRepeatingTimerCallCount = 0
var lastMockTimer: MockTimer?
func testSomething() {
let sut = TimerController(seconds: 12, makeRepeatingTimer: { [unowned self] interval, closure in
self.makeRepeatingTimerCallCount += 1
self.lastMockTimer = MockTimer(interval: interval, closure: closure)
return self.lastMockTimer!
})
// call something on sut
// verify against makeRepeatingTimerCallCount and lastMockTimer
}
}

Related

How to Test Timer with #Published?

I created a view which uses a ObservableObject which used an Timer to update seconds which are an #Published property.
class TimerService: ObservableObject {
#Published var seconds: Int
var timer: Timer?
convenience init() {
self.init(0)
}
init(_ seconds: Int){
self.seconds = seconds
}
func start() {
...
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.seconds += 1 }
self.timer?.fire()
}
func stop() {...}
func reset() {...}
}
To test this logic I tried to subscribe to the seconds var. The problem is that the .sink method only trigger once and never again, even when it should.
class WorkTrackerTests: XCTestCase {
var timerService: TimerService!
override func setUpWithError() throws {
super.setUp()
timerService = TimerService()
}
override func tearDownWithError() throws {
super.tearDown()
timerService = nil
}
func test_start_timer() throws {
var countingArray: [Int] = []
timerService.start()
timerService.$seconds.sink(receiveValue: { value -> Void in
print(value) // 1 (called once with this value)
countingArray.append(value)
})
timerService.stop()
for index in 0...countingArray.count-1 {
if(index>0) {
XCTAssertTrue(countingArray[index] - 1 == countingArray[index-1])
}
}
}
}
Is there something I did wrong or is the SwiftUI #Published Wrapper not capable of being subscribed by something else than SwiftUI itself?
I'll start by repeating what I said already in comments. There is no need to test Apple's code. Don't test Timer. You know what it does. Test your code.
As for your actual example test harness, it is flawed from top to bottom. A sink without a store will indeed get only one value, if it gets any at all. But the issue runs even deeper, as you are acting like your code will magically stop and wait for the timer to finish. It won't. You are saying stop immediately after saying start, so the timer never even runs. Asynchronous input requires asynchronous testing. You would need an expectation and a waiter.
But it is very unclear why you are subscribing to the publisher at all. What are you trying to find out? The only question of interest, it seems, is whether you are incrementing your variable each time the timer fires. And you can test that without subscribing to a publisher — and, as I said, without a Timer.
So much for the repetition. Now let's demonstrate. Let's start with the code you've actually shown, the only code that has content:
class TimerService: ObservableObject {
#Published var seconds: Int
var timer: Timer?
convenience init() {
self.init(0)
}
init(_ seconds: Int){
self.seconds = seconds
}
func start() {
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.seconds += 1
}
self.timer?.fire()
}
}
Now look at all the commands you send to the Timer and encapsulate them in a Timer subclass:
class TimerMock : Timer {
var block : ((Timer) -> Void)?
convenience init(block: (#escaping (Timer) -> Void)) {
self.init()
self.block = block
}
override class func scheduledTimer(withTimeInterval interval: TimeInterval,
repeats: Bool,
block: #escaping (Timer) -> Void) -> Timer {
return TimerMock(block:block)
}
override func fire() {
self.block?(self)
}
}
Now make your TimerService a generic so that we can inject TimerMock when testing:
class TimerService<T:Timer>: ObservableObject {
#Published var seconds: Int
var timer: Timer?
convenience init() {
self.init(0)
}
init(_ seconds: Int){
self.seconds = seconds
}
func start() {
self.timer = T.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.seconds += 1
}
self.timer?.fire()
}
}
So now we can test your logic without bothering to run a timer:
import XCTest
#testable import TestingTimer // whatever the name of your module is
class TestingTimerTests: XCTestCase {
func testExample() throws {
let timerService = TimerService<TimerMock>()
timerService.start()
if let timer = timerService.timer {
timer.fire()
timer.fire()
timer.fire()
}
XCTAssertEqual(timerService.seconds,4)
}
}
None of your other code needs to change; you can go on using TimerService as before. I can think of other ways to do this, but they would all involve dependency injection where in "real life" you use a Timer but when testing you use a TimerMock.

AudioKit: can't restart AudioKit sampling of frequency after calling 'AudioKit.stop()'

I'm using AudioKit 4.10.
I use this simple code to sample frequency and amplitude from a microphone:
import AudioKit
protocol MicAnalyzerDelegate {
/**
* The MicAnalyzer calls this delegate function.
*/
func micAnalyzerInfos(infos: (frequency : Double, amplitude : Double)?)
}
class MicAnalyzer: NSObject {
// MARK: - Properties
private static var micAnalyzer: MicAnalyzer = MicAnalyzer()
var delegate: MicAnalyzerDelegate?
fileprivate var mic: AKMicrophone!
fileprivate var tracker: AKFrequencyTracker!
fileprivate var silence: AKBooster!
fileprivate var timer: Timer?
// MARK: - Initializations
private override init() {
super.init()
AKSettings.audioInputEnabled = true
self.initMicTracker()
}
private func initMicTracker(){
/* Add the built-in microphone. */
mic = AKMicrophone()
/* Add a traker */
tracker = AKFrequencyTracker(mic)
silence = AKBooster(tracker, gain: 0)
}
// MARK: - Accessors
class func shared() -> MicAnalyzer {
return micAnalyzer
}
func startMonitoring() {
/* Start the microphone and analyzer. */
AudioKit.output = silence
do {
try AudioKit.start()
} catch {
AKLog("AudioKit did not start!")
}
/* Initialize and schedule a new run loop timer. */
timer = Timer.scheduledTimer(timeInterval: 0.1,
target: self,
selector: #selector(MicAnalyzer.tick),
userInfo: nil,
repeats: true)
}
// Stopped as described here: https://github.com/AudioKit/AudioKit/issues/1716
func stopMonitoring() {
do {
AudioKit.disconnectAllInputs()
try AudioKit.stop()
try AudioKit.shutdown()
} catch {
print("AudioKit did not stop")
}
AudioKit.output = nil
// Interrupts polling on tick infos
timer?.invalidate()
timer = nil
}
var infos: (frequency : Double, amplitude : Double)? {
if(!tracker.isStopped){
let frequency = tracker.frequency
let amplitude = tracker.amplitude
return (frequency: frequency, amplitude: amplitude)
} else {
return nil
}
}
/* Call the delegate. */
#objc func tick() {
self.delegate?.micAnalyzerInfos(infos: infos)
}
}
Calling in order the following code I have that situation:
MicAnalyzer.shared().startMonitoring() // --> Everything works good
MicAnalyzer.shared().stopMonitoring() // --> I think it is stopped correctly
MicAnalyzer.shared().startMonitoring() // --> From now on the delegate is called but I get always 0 as frequency and amplitude
Why after recalling the AudioKit.start() (inside the MicAnalyzer.shared().startMonitoring()) I can't sample frequency and amplitude anymore? How should I restart the whole thing?
If I reinitiate each time the variables mic, tracker and silence the memory grows as they are not really deallocated.
This is a second version of the same code idea:
import AudioKit
protocol MicAnalyzerDelegate : class {
/**
* The tuner calls this delegate function
*/
func micAnalyzerInfos(infos: (frequency : Double, amplitude : Double)?)
}
// https://audiokit.io/examples/MicrophoneAnalysis/
class MicAnalyzer: NSObject {
// MARK: - Properties
private weak var delegate: MicAnalyzerDelegate?
fileprivate var mic: AKMicrophone!
fileprivate var tracker: AKFrequencyTracker!
fileprivate var silence: AKBooster!
fileprivate var timer: Timer?
// MARK: - Initializations
init(delegate: MicAnalyzerDelegate) {
print("Init called!!!")
self.delegate = delegate
super.init()
}
deinit {
print("Deinit called!!!")
}
// MARK: - Accessors
func startMonitoring() {
AKSettings.audioInputEnabled = true
/* Add the built-in microphone. */
mic = AKMicrophone()
/* Add a traker */
tracker = AKFrequencyTracker(mic)
silence = AKBooster(tracker, gain: 0)
/* Start the microphone and analyzer. */
AudioKit.output = silence
do {
try AudioKit.start()
} catch {
AKLog("AudioKit did not start!")
}
/* Initialize and schedule a new run loop timer. */
timer = Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(MicAnalyzer.tick),
userInfo: nil,
repeats: true)
}
func stopMonitoring() {
do {
AudioKit.disconnectAllInputs()
try AudioKit.stop()
try AudioKit.shutdown()
} catch {
print("AudioKit did not stop")
}
AudioKit.output = nil
// Interrupts polling on tick infos
timer?.invalidate()
timer = nil
silence.stop()
silence = nil
tracker.stop()
tracker = nil
mic.stop()
mic = nil
}
var infos: (frequency : Double, amplitude : Double)? {
if(!tracker.isStopped){
let frequency = tracker.frequency
let amplitude = tracker.amplitude
return (frequency: frequency, amplitude: amplitude)
} else {
return nil
}
}
/* Call the delegate. */
#objc func tick() {
print(infos?.frequency ?? "0.0")
//self.delegate.micAnalyzerInfos(infos: infos)
}
}
And then I could call that 'second code test' like this in order:
override func viewDidAppear() {
super.viewDidAppear()
print("viewDidAppear!!!")
tuner = Tuner(delegate: self)
tuner?.startMonitoring()
}
override func viewDidDisappear() {
super.viewDidDisappear()
print("viewDidDisappear!!!")
tuner?.stopMonitoring()
tuner = nil
}
You need to make sure the AKFrequencyTracker is part of the signal chain, as that's an AVAudioEngine engine requirement.
Which means that if you do:
do {
AudioKit.disconnectAllInputs()
try AudioKit.stop()
try AudioKit.shutdown()
} catch {
// error handler
}
AudioKit.output = nil
You have to call initMicTracker to have the AKFrequencyTracker back in the signal chain. So, to restart you'd do:
initMicTracker
startMonitoring
Because your stopMonitoring did:
AudioKit.disconnectAllInputs(), which means (stop)MicTracker
You know that you already created instances for AKMicrophone, AKFrequencyTracker and AKBooster free it from memory
func stopMonitoring() {
...
self.mic = nil
self.tracker = nil
self.silence = nil
}

Stop and restart a timer

I want to stop this timer and then restart it from where I stopped it.
secondsTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(addSeconds), userInfo: nil, repeats: true)
Below, it was suggested I shouldn't increment a timer in my timer handler. Why not?
For example, using GCD timer:
func countSeconds() {
secondsTimer = DispatchSource.makeTimerSource(queue: .main)
secondsTimer?.schedule(deadline: .now(), repeating: 1.0)
secondsTimer?.setEventHandler { [weak self] in
self?.addSeconds()
}
}
#objc func addSeconds() {
seconds += 1
}
func startGame() {
secondsTimer?.resume()
}
We don't pause/resume Timer instances. We stop them with invalidate(). And when you want to restart it, just create new timer.
Please refer to the Timer documentation, also available right in Xcode.
Note that you can suspend and resume GCD timers, DispatchSourceTimer.
var timer: DispatchSourceTimer? // note, unlike `Timer`, we have to maintain strong reference to GCD timer sources
func createTimer() {
timer = DispatchSource.makeTimerSource(queue: .main)
timer?.schedule(deadline: .now(), repeating: 1.0)
timer?.setEventHandler { [weak self] in // assuming you're referencing `self` in here, use `weak` to avoid strong reference cycles
// do something
}
// note, timer is not yet started; you have to call `timer?.resume()`
}
func startTimer() {
timer?.resume()
}
func pauseTiemr() {
timer?.suspend()
}
func stopTimer() {
timer?.cancel()
timer = nil
}
Please note, I am not suggesting that if you want suspend and resume that you should use GCD DispatchSourceTimer. Calling invalidate and recreating Timer as needed is simple enough, so just do that. I only provide this GCD information for the sake of completeness.
By the way, as a general principle, never "increment" some counter in your timer handler. That's a common mistake. Timers are not guaranteed to fire every time or with exact precision. Always save some reference time at the start, and then in your event handler, calculate differences between the current time and the start time. For example, extending my GCD timer example:
func createTimer() {
timer = DispatchSource.makeTimerSource(queue: .main)
timer?.schedule(deadline: .now(), repeating: 0.1)
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.hour, .minute, .second, .nanosecond]
formatter.zeroFormattingBehavior = .pad
timer?.setEventHandler { [weak self] in
guard let start = self?.start else { return }
let elapsed = (self?.totalElapsed ?? 0) + CACurrentMediaTime() - start
self?.label.text = formatter.string(from: elapsed)
}
}
var start: CFTimeInterval? // if nil, timer not running
var totalElapsed: CFTimeInterval?
#objc func didTapButton(_ button: UIButton) {
if start == nil {
startTimer()
} else {
pauseTimer()
}
}
private func startTimer() {
start = CACurrentMediaTime()
timer?.resume()
}
private func pauseTimer() {
timer?.suspend()
totalElapsed = (totalElapsed ?? 0) + (CACurrentMediaTime() - start!)
start = nil
}
I do it with this code:
var timer: Timer?
func startTimer() {
timer = .scheduledTimer(withTimeInterval: 4, repeats: false, block: { _ in
// Do whatever
})
}
func resetTimer() {
timer?.invalidate()
startTimer()
}
You can start, stop and reset timer in swift4
class ViewController: UIViewController {
var counter = 0
var timer = Timer()
var totalSecond = 20
#IBOutlet weak var label1: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func start_btn(_ sender: Any) {
timer.invalidate() // just in case this button is tapped multiple times
timer = Timer.scheduledTimer(timeInterval: 0.5, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true)
}
#IBAction func stop_btn(_ sender: Any) {
do {
self.timer.invalidate()
}
func timeFormatted(_ totalSeconds: Int) -> String {
let seconds: Int = totalSeconds % 60
return String(format: "0:%02d", seconds)
}
}
#IBAction func reset_btn(_ sender: Any) {
timer.invalidate()
//timerAction()
counter = 0
label1.text = "\(counter)"
}
#objc func timerAction()
{
counter += 1
label1.text = "\(counter)"
}
}
You can declare the Timer as 'weak var' instead of just 'var' like:
weak var timer: Timer?
Now you can pause your timer with:
timer?.invalidate()
To resume:
timer?.fire()

In Swift, how can I unit test something dependent on a delay implemented with a private NSTimer?

I have a class that uses an NSTimer to buffer a change to one of its stored properties. I'm having trouble unit testing this class without having to expose the private properties and methods. Here is an illustrative version:
import CoreLocation
class BeaconActivity {
private let reaction: BeaconActivity -> ()
private var timer = NSTimer()
private(set) var proximity = CLProximity.Unknown {
didSet {
self.reaction(self)
}
}
init(reaction: BeaconActivity -> ()) {
self.reaction = reaction
}
func startUpdate(proximity: CLProximity) {
self.timer.invalidate()
self.timer = NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: "completeUpdate:", userInfo: proximity.rawValue, repeats: false)
}
dynamic private func completeUpdate(timer: NSTimer) {
let rawValue = timer.userInfo as! Int
self.proximity = CLProximity(rawValue: rawValue)!
}
}
For example, how would I test that reaction that gets passed into the init runs when the proximity property is updated - without having to put a "sleep" in my test code?

How to pass callback functions in Swift

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
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
}
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)")
}
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.