Swift: Create Class with NSTimer - swift

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

Related

delegate method does not get called second time

I am building a simple currency converter app. When ViewController gets opened it calls a function from CoinManager.swift:
class ViewController: UIViewController {
var coinManager = CoinManager()
override func viewDidLoad() {
super.viewDidLoad()
coinManager.delegate = self
coinManager.getCoinPrice(for: "AUD", "AZN", firstCall: true)
}
...
}
CoinManager.swift:
protocol CoinManagerDelegate {
func didUpdatePrice(price1: Double, currency1: String, price2: Double, currency2: String)
func tellTableView(descriptions: [String], symbols: [String])
func didFailWithError(error: Error)
}
struct CoinManager {
var delegate: CoinManagerDelegate?
let baseURL = "https://www.cbr-xml-daily.ru/daily_json.js"
func getCoinPrice (for currency1: String,_ currency2: String, firstCall: Bool) {
if let url = URL(string: baseURL) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}
if let safeData = data {
if let coinData = self.parseJSON(safeData) {
if firstCall {
var descriptions = [""]
let listOfCoins = Array(coinData.keys)
for key in listOfCoins {
descriptions.append(coinData[key]!.Name)
}
descriptions.removeFirst()
self.delegate?.tellTableView(descriptions: descriptions, symbols: listOfCoins)
}
if let coinInfo1 = coinData[currency1] {
let value1 = coinInfo1.Value
if let coinInfo2 = coinData[currency2] {
let value2 = coinInfo2.Value
//this line does not do anything the second time I call getCoinPrice:
self.delegate?.didUpdatePrice(price1: value1, currency1: currency1, price2: value2, currency2: currency2)
//And this one does work
print("delegate:\(currency1)")
} else {
print("no name matches currency2")
}
} else {
print("no name matches currency1")
}
}
}
}
task.resume()
}
}
func ParseJSON....
}
The method it calls (ViewController.swift):
extension ViewController: CoinManagerDelegate {
func didUpdatePrice(price1: Double, currency1: String, price2: Double, currency2: String) {
print("didUpdatePrice called")
DispatchQueue.main.async {
let price1AsString = String(price1)
let price2AsString = String(price2)
self.leftTextField.text = price1AsString
self.rightTextField.text = price2AsString
self.leftLabel.text = currency1
self.rightLabel.text = currency2
}
}
...
}
and finally, CurrencyViewController.swift:
var coinManager = CoinManager()
#IBAction func backButtonPressed(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
coinManager.getCoinPrice(for: "USD", "AZN", firstCall: false)
}
So when I launch the app i get following in my debug console:
didUpdatePrice called
delegate:AUD
And when I call getCoinPrice() from CurrencyViewController the delegate method does not get called. I know that my code goes through the delegate function line as I get this in debug console:
delegate:USD
I just can't wrap my head around it. The delegate method does not work when gets called second time. Even though it is called by the same algorithm
It's because you're creating a new object of CoinManager in CurrencyViewController where the delegate is not set. So you've to set the delegate every time you create a new instance of CoinManager.
#IBAction func backButtonPressed(_ sender: UIBarButtonItem) {
dismiss(animated: true, completion: nil)
coinManager.delegate = self
coinManager.getCoinPrice(for: "USD", "AZN", firstCall: false)
}
Update: So, the above solution would require for you to make the delegate conformance in CurrencyViewController. If you're looking for an alternate solution you should probably pass the instance of coinManager in ViewController to CurrencyViewController. For that here are the things you need to update.
In CurrencyViewController:
class CurrencyViewController: UIViewController {
var coinManager: CoinManager! // you can optional unwrap if you intent to use CurrencyViewController without coinManager
//...
And in ViewController:
currencyViewController.coinManager = coinManager // passing the instance of coinManager
Can you share the full code of CoinManager? I see this part
if firstCall {
...
}
Maybe some block logic here or unhandled cases? And can you share the full code of protocol?
Also try to print something before this code:
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}

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

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

Settings as shared Instance

I tried to do this so get my settings saved whenever the App moves to the background or gets killed or whatever.
I want to access and set the property "useLimits" all over my App.
Why is it not working?
Is there a better more elegant way to achieve this?
import UIKit
class Settings: NSObject
{
static let sharedInstance = Settings()
private let kUseLimits = "kUseLimits"
var useLimits = false
override init()
{
super.init()
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: #selector(Settings.save),
name: UIApplicationWillResignActiveNotification,
object: nil)
let userdefaults = NSUserDefaults.standardUserDefaults()
self.useLimits = userdefaults.boolForKey(kUseLimits)
}
deinit
{
NSNotificationCenter.defaultCenter().removeObserver(self)
save()
}
func save()
{
let userdefaults = NSUserDefaults.standardUserDefaults()
userdefaults.setBool(self.useLimits, forKey: kUseLimits)
userdefaults.synchronize()
}
func reset()
{
self.useLimits = false
save()
}
}
I think something like this will be good:
class AppSettings {
private struct Keys {
static let useLimits = "AppSttings.useLimits"
}
static var useLimits: Bool {
set {
NSUserDefaults.standardUserDefaults().setBool(newValue, forKey: Keys.useLimits)
}
get {
return NSUserDefaults.standardUserDefaults().boolForKey(Keys.useLimits)
}
}
static func rest() {
useLimits = false
}
}
P.S. Starting from iOS 8 you don't need to call synchronize() in NSUserDefault
P.S.S. NSUserDefaults.standardUserDefaults().boolForKey(Keys.useLimits) will return false if there not such object, if you need specific default value please check on object or use NSUserDefaults.standardUserDefaults().registerDefaults()
P.S.S.S. It wont effect your performance much, so you can read from UD and write there just on on the run, but if you want too performance code, you can do something like this:
private static var _useLimits: Bool?
static var useLimits: Bool {
set {
NSUserDefaults.standardUserDefaults().setBool(newValue, forKey: Keys.useLimits)
_useLimits = newValue
}
get {
if _useLimits == nil {
_useLimits = NSUserDefaults.standardUserDefaults().boolForKey(Keys.useLimits)
}
return _useLimits!
}
}
or more elegant for current value:
private static var _useLimits: Bool = NSUserDefaults.standardUserDefaults().boolForKey(Keys.useLimits)
static var useLimits: Bool {
set {
NSUserDefaults.standardUserDefaults().setBool(newValue, forKey: Keys.useLimits)
_useLimits = newValue
}
get {
return _useLimits
}
}

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?