Change userInfo in timer selector function in Swift - swift

I want to update the userInfo of the timer in the selector function every time the timer fires.
userInfo:
var timerDic = ["count": 0]
Timer:
Init: let timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("cont_read_USB:"), userInfo: timerDic, repeats: true)
selector function:
public func cont_read_USB(timer: NSTimer)
{
if var count = timer.userInfo?["count"] as? Int
{
count = count + 1
timer.userInfo["count"] = count
}
}
I get an error on the last line:
'AnyObject?' does not have a member named 'subscript'
What is wrong here?
In Objective_C this task worked with a NSMutableDictionary as userInfo

To make this work, declare timerDic as an NSMutableDictionary:
var timerDic:NSMutableDictionary = ["count": 0]
Then in your cont_read_USB function:
if let timerDic = timer.userInfo as? NSMutableDictionary {
if let count = timerDic["count"] as? Int {
timerDic["count"] = count + 1
}
}
Discussion:
Swift dictionaries are value types, so if you want to be able to update it you have to pass an object. By using an NSMutableDictionary you get an object type that is passed by reference, and it can be modified since it is a mutable dictionary.
Complete example for Swift 4+:
If you don't want to use an NSMutableDictionary, you can create your own class. Here is a complete example using a custom class:
import UIKit
class CustomTimerInfo {
var count = 0
}
class ViewController: UIViewController {
var myTimerInfo = CustomTimerInfo()
override func viewDidLoad() {
super.viewDidLoad()
_ = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(update), userInfo: myTimerInfo, repeats: true)
}
#objc func update(_ timer: Timer) {
guard let timerInfo = timer.userInfo as? CustomTimerInfo else { return }
timerInfo.count += 1
print(timerInfo.count)
}
}
When you run this in the simulator, the count that is printed increases every second.

NSTimer.userInfo is of type AnyObject so you need to cast it to your target object:
public func cont_read_USB(timer: NSTimer)
{
if var td = timer.userInfo as? Dictionary<String,Int> {
if var count = td["count"] {
count += 1
td["count"] = count
}
}
}

Related

Triggering event only on a change of variable output

I have created the following coin toss program which also triggers an audio file with each coin toss every second. I'd like to know how to change this so that the audio only triggers when the result changes from a heads to a tails or vice versa. Not every second as it is now. Any help is greatly appreciated.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var times: Timer!
var timer: Timer!
var coinFlip : Int = 0
var coinResult : String = ""
var audioPlayer : AVAudioPlayer!
let soundArray = ["note1", "note7"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
times = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(fiftyFifty), userInfo: nil, repeats: true)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(result), userInfo: nil, repeats: true)
result()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func fiftyFifty() -> Int {
coinFlip = Int(arc4random_uniform(2))
return coinFlip
}
func result() -> String {
if coinFlip == 1 {
coinResult = "Heads"
print("Heads")
playSound(soundFileName: soundArray[0])
}
else {
coinResult = "Tails"
print("Tails")
playSound(soundFileName: soundArray[1])
}
}
func playSound(soundFileName : String) {
let soundURL = Bundle.main.url(forResource: soundFileName, withExtension: "wav")
do {
audioPlayer = try AVAudioPlayer(contentsOf: soundURL!)
}
catch {
print(error)
}
audioPlayer.play()
}
}
I've now changed it to add a new variable output from result(), coinResult. Not sure if this helps but hopefully might.
If I understood well, when the coin flips, you want to play a song, in this case you can simply change your coinFlip declaration to that:
var coinFlip : Int = 0 {
didSet {
result()
}
}
didSet is a property observer that observes and respond to changes in property's value.
didSet is called immediately after the new value is stored.
You can read more about property observers in Apple's documentation.
EDIT: Your code will be like this:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var times: Timer!
var timer: Timer!
// Change this
var coinFlip : Int = 0 {
didSet {
result()
}
}
var audioPlayer : AVAudioPlayer!
let soundArray = ["note1", "note7"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
times = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(fiftyFifty), userInfo: nil, repeats: true)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(result), userInfo: nil, repeats: true)
result()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func fiftyFifty() -> Int {
coinFlip = Int(arc4random_uniform(2))
return coinFlip
}
func result() {
if coinFlip == 1 {
print("Heads")
playSound(soundFileName: soundArray[0])
}
else {
print("Tails")
playSound(soundFileName: soundArray[1])
}
}
func playSound(soundFileName : String) {
let soundURL = Bundle.main.url(forResource: soundFileName, withExtension: "wav")
do {
audioPlayer = try AVAudioPlayer(contentsOf: soundURL!)
}
catch {
print(error)
}
audioPlayer.play()
}
}

Reporting changes from child ViewController

If one ViewController inherits from another, how do I update stuff in the child ViewController as the variable changes in the parent ViewController?
class ViewControllerOne: UIViewController {
var timer = Timer()
var number: Int = 0
func updateNumber() {
number += 1
}
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateNumber), userInfo: nil, repeats: true)
}
}
class ViewControllerTwo: ViewControllerOne {
// So in this class I want to print to the console when number is 3.
// How do I check for that from this class?
}
Override updateNumber in ViewControllerTwo.
override func updateNumber() {
super.updateNumber()
if number == 3 {
// do something
}
}

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

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?

Swift: Create Class with NSTimer

I want to create a DirectoryWatcher class that detects whether a directory has changed its first level content and then calls a delegate. This will also run in another thread.
Here is my code:
protocol DirectoryWatcherDelegate {
func directoryDidChange(path: String)
}
class DirectoryWatcher {
private(set) var path = ""
private let timer: NSTimer
private let fm = NSFileManager.defaultManager()
private var previousContent: [String]
let delegate: DirectoryWatcherDelegate?
init(initPath: String) {
path = initPath
timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector: Selector("checkDir"), userInfo: nil, repeats: true)
}
func start() {
previousContent = getContentAtPath()
timer.fire()
}
func stop() {
timer.invalidate()
}
private func checkDir() {
if previousContent != getContentAtPath() {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if let delegate = self.delegate {
delegate.directoryDidChange(self.path)
} else {
print("No delegate has been assigned")
}
})
}
}
private func getContentAtPath() -> [String] {
var content: [String]
do {
content = try fm.contentsOfDirectoryAtPath(path)
} catch {
content = []
}
return content
}
}
My Problem ist in the init methode, where I initialize the NSTimer. Because this is the init methode I am not able to use self, bacause it is uninitialized.
How to fix this?
BTW: The error messages I am getting are:
The variable self.timer used before initialized
and
Return from initializer without initializing all stored properties