How to pass callback functions in Swift - 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.

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.

Swift closure defindet as class property cannot access other class properies

When directly assigning / implementing a Swift closure it is no problem to access class properties. But when I try to define the closure as class propertie as well, access to other class properties is not possible. Why is this?
Here is an example:
While the closure directly assigned to editorVC.completionBlock can access the class property tableView without any problem, the same code within leads to an error when the closure is defined as class property editorCompletionBlock:
class MyViewController: UIViewController {
#IBOutlet var tableView: UITableView!
func showEditor(withData: String) {
let editorVC = EditorViewController()
// Directly assign closure - Works without any problem
editorVC.completionBlock = { (result) in
self.tableView.reloadData()
doSomething(withResult: result)
// ...
}
present(editorVC, animated: true, completion: nil)
}
// Define closure as class property ==> Error
let editorCompletionBlock: EditorCompletionBlock = { (resut) in
// ERROR: Value of type '(MyViewController) -> () -> MyViewController' has no member 'tableView'
self.tableView.reloadData()
doSomething(withResult: result)
// ...
}
}
typealias EditorCompletionBlock = (String) -> Void
class EditorViewController: UIViewController {
var completionBlock: EditorCompletionBlock?
func closeEditor(withResult result: String) {
completionBlock?(result)
}
}
Reason:
You can't access self until the initialization process of a type is completed.
In your code, editorCompletionBlock is a stored property and you're trying to access self.tableView inside it. This is the reason it is giving compile time error.
Solution:
Instead, make editorCompletionBlock as a lazy property to get that working.
lazy var editorCompletionBlock: EditorCompletionBlock = { (result) in
self.tableView.reloadData()
doSomething(withResult: result)
}
Solution 1:
place your editorCompletionBlock in viewDidLoad(:_) and it should work like charm:
class MyViewController: UIViewController {
#IBOutlet var tableView: UITableView!
func showEditor(withData: String) {
let editorVC = EditorViewController()
editorVC.completionBlock = { (result) in
self.tableView.reloadData()
}
present(editorVC, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let editorCompletionBlock: EditorCompletionBlock = { (resut) in
self.tableView.reloadData() ///should work :)
}
}
}
typealias EditorCompletionBlock = (String) -> Void
class EditorViewController: UIViewController {
var completionBlock: EditorCompletionBlock?
func closeEditor(withResult result: String) {
completionBlock?(result)
}
}
Solution 2:
Or you can just declare your closure as lazy:
lazy var editorCompletionBlock: EditorCompletionBlock = { (result)
in
self.tableView.reloadData()
doSomething(withResult: result)
}

Swift Delegates not working for updating timer to receiver

I have created protocol as updateTime protocol to update timer to other view controllers conform to updateTime delegates but the timer not updating to other controllers.
Please refer below code and pin point where I did wrong?
protocol UpdateTime: class {
func updateTIme(count: Int)
}
class ViewController: UIViewController, UpdateTime {
#IBOutlet var timeLabel: UILabel!
let secVC = SecondViewController()
override func viewDidLoad() {
super.viewDidLoad()
secVC.delegate = self
// Do any additional setup after loading the view.
}
// must be internal or public.
func updateTIme(count: Int){
print("firstVC: \(count)")
DispatchQueue.main.async {
self.timeLabel.text = "\(count)"
}
}
}
class Counter {
var count = 0
func increment() {
count += 1
}
func increment(by amount: Int) -> Int{
count += amount
print(count)
return count
}
func reset() {
count = 0
}
}
This is seconviewcontroller:
class SecondViewController: UIViewController {
#IBOutlet var label: UILabel!
weak var delegate: UpdateTime?
var counter = Counter()
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
DispatchQueue.main.async {
self.timer.invalidate()
self.timer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(self.countDown), userInfo: nil, repeats: true)
}
}
#objc func countDown() -> Int{
// counter increments
counter.increment()
let count = counter.increment(by: 5)
DispatchQueue.main.async {
self.label.text = String(describing: count)
}
self.delegate?.updateTIme(count: count)
return count
}
}
The label in view controller not updating count down.
This looks like a lifecycle issue. Though you are creating an instance of SecondViewController, the timer from the second view controller only gets added to the run loop when that view controller's viewDidLoad method is called. Since you are programmatically creating it in the first view controller, there is nowhere in your example that would cause the second view controller's view to get loaded, so the timer would not be added to the runloop.
A better way of accomplishing this issue is to create the timer on the first view controller directly, or create a secondary object (not a second view controller) that manages the timer and have it push updates to the first view controller

How Can I Unit Test Swift Timer Controller?

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
}
}

Post-creation callback: How to run code immediately after ctor?

I wonder if I can get a callback after init(), or perhaps before deinit().
I have this repeating pattern in my code. First init() is run. Next fire() function is run.
class ParentViewController: UIViewController {
#IBAction func redButtonAction(sender: AnyObject) {
PickedColorEvent(color: UIColor.redColor(), name: "RED").fire()
}
#IBAction func greenButtonAction(sender: AnyObject) {
PickedColorEvent(color: UIColor.greenColor(), name: "GREEN").fire()
}
#IBAction func blueButtonAction(sender: AnyObject) {
PickedColorEvent(color: UIColor.blueColor(), name: "BLUE").fire()
}
#IBAction func resetButtonAction(sender: AnyObject) {
ResetEvent().fire()
}
}
Desired code, without calling the fire() function. I want it to autofire after creation. My code always run on the main-thread, so I guess I could use a one-shot timer for this. However I'm looking for a solution without timers.
class ParentViewController: UIViewController {
#IBAction func redButtonAction(sender: AnyObject) {
PickedColorEvent(color: UIColor.redColor(), name: "RED")
}
#IBAction func greenButtonAction(sender: AnyObject) {
PickedColorEvent(color: UIColor.greenColor(), name: "GREEN")
}
#IBAction func blueButtonAction(sender: AnyObject) {
PickedColorEvent(color: UIColor.blueColor(), name: "BLUE")
}
#IBAction func resetButtonAction(sender: AnyObject) {
ResetEvent()
}
}
Is it possible to get rid of the fire() function?
Update 1 (thanks feedback from #luk2302)
The event classes inherit from a protocol. I could change the protocol to a class or a struct, and call fire() within the base class. However then I decide wether the subclasses can be struct or class.
When just invoking ResetEvent() without using the result, then I get the Result of initializer is unused. I wish there was a reserved keyword for suppressing that warning.
you can use fire function after you initialised all stored properties as you can call any other function defined in your class and / or struct
protocol P {}
extension P {
func fire(s: String) {
print("fire from \(s)")
}
}
class C:P {
var i: Int
init(i:Int) {
self.i = i
foo()
fire("class init i: \(self.i)")
}
deinit {
fire("class deinit i: \(i)")
}
func foo() {
i += 1
}
}
struct S:P {
var i: Int
init(i: Int){
self.i = i
foo()
fire("struct init i: \(self.i)")
}
mutating func foo() {
i += 10
}
}
C(i: 5)
S(i: 1)
/*
fire from class init i: 6
fire from struct init i: 11
fire from class deinit i: 6
*/
If you want to avoid compiler warning "Result of initializer is unused", just use next syntax
_ = C(i: 5)
_ = S(i: 1)