I've written a test that checks if some value will go down to zero until 5 seconds. Every each second NSTimer fires a method that decrements this value by one.
It looks like this:
let foo = SomeObject(initWithTime: 5)
let expectation = expectationWithDescription("Ohai")
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: foo, selector: Selector("decrement"), userInfo: nil, repeats: true)
XCTAssertTrue(foo.isFinished, "Foo is finished, so it should be true")
expectation.fulfill()
waitForExpectationsWithTimeout(5.0, handler: nil)
isFinished sets to true when foo reaches zero. Timer works fine, but expectation doesn't wait for 5 seconds, just crashes instantly. What's happening?
It seems that your XCTAssertTrue fail immediately since foo is probably not finished when it is called immediately.
Related
I'm a beginner in Swift and have a task to change the bottom sheet message when the process of the app doesn't work in three minutes. So, the message will change from "available" to "not available" if the process does not work.
I found code syntax like:
Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true)
What I think:
var waktu = 0
Timer.scheduledTimer(withTimeInterval: 180.0, repeats: false) {
if waktu == 180 {
timer.invalidate()
//run the change message function
}
}
You can add observer on your property, so it will invalidate the timer when condition met, like so:
var waktu = 0 {
didSet {
if waktu == 180 {
timer.invalidate()
}
}
}
Your code creates a timer that will fire once in 3 minutes.
You’re using a variation on the timer method you list, scheduledTimer(withTimeInterval:repeats:block:)
That variant takes a closure instead of a selector. Then you’re using “trailing closure syntax” to provide that closure in braces outside the closing paren for the function call. That’s all good.
However, you define a variable waktu And give it a starting value of 0. You don’t show any code that will change that value, so your if statement if waktu == 180 will never evaluate to true and run your change message function. You need to update that if statement to determine if “the process of the app works”, whatever that means. (Presumably you have some definition of what your app working/not working means, and can update that if statement accordingly.)
let _ = Timer.scheduledTimer(withTimeInterval: 4.0, repeats: true) { (timer) in
//do something
}
I create a timer and invoke its block every 4 second. Then I make application to enter background and enter foreground after a while. But it could invoke the block quickly even less than 1 second sometimes.
I have a timer, which is a singleton, that repeatedly fires every second. I allow the user to pause the timer as well as resume. I am keeping track of the start date of the timer, and I am subtracting any pauses from the elapsed time.
Unfortunately, I can't seem to fix an intermittent issue where pausing and resuming the timer causes a skipping of one second.
For instance, in the following code block, I start the timer and print the seconds:
1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
9.0
10.0
11.0
12.0
13.0
14.0
15.0
16.0
17.0
18.0
19.0
In the following code block, I resume the timer, printing the seconds again. However, as you can see, the 20th second has not been printed:
21.0
22.0
23.0
24.0
25.0
26.0
I cannot seem to figure out where I am losing the second. It does not happen with each pause and resume cycle.
The properties that I am using to keep track of the aforementioned are as follows:
/// The start date of the timer.
private var startDate = Date()
/// The pause date of the timer.
private var pauseDate = Date()
/// The number of paused seconds.
private var paused = TimeInterval()
/// The number of seconds that have elapsed since the initial fire.
private var elapsed = TimeInterval()
I start the timer by creating the timer and setting the start date:
/// Starts the shower timer.
func startTimer() {
// Fire the timer every second.
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
// Set the start time of the initial fire.
startDate = Date()
}
If the user pauses the timer, then the following method executes:
/// Pauses the shower timer.
func pauseTimer() {
// Pause the timer.
timer?.invalidate()
// Set the timer to `nil`, according to the documentation.
timer = nil
// Set the date of the pause.
pauseDate = Date()
}
Then, the following method executes when the user resumes the timer:
/// Resumes the timer.
func resumeTimer() {
// Recreate the timer.
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
// Add the number of paused seconds to the `paused` property.
paused += Date().timeIntervalSince(pauseDate)
}
The following method, which is called by the method that executes when the timer fires, sets the number of elapsed seconds since the initial fire, less the sum of any pauses:
/// Sets the number of elapsed seconds since the timer has been started, accounting for pauses, if any.
private func updateElapsedTime() {
// Get the date for now.
let now = Date()
// Get the time that has elapsed since the initial fire of the timer, and subtract any pauses.
elapsed = now.timeIntervalSince(startDate).rounded(.down).subtracting(paused.rounded(.down))
}
Finally, the following method is the Selector that executes when the timer fires:
/// Updates the number of elapsed seconds since the timer has been firing.
#objc private func updateElapsedSeconds() {
// Configure the elapsed time with each fire.
updateElapsedTime()
// Post a notification when the timer fires, passing a dictionary that includes the number of elapsed seconds.
NotificationCenter.default.post(name: CustomNotification.showerTimerFiredNotification, object: nil, userInfo: nil)
}
What am I doing incorrectly to cause a missing second intermittently?
So the issue here is that Timer is not accurate in this way. Or rather, its timekeeping is reasonably accurate, but the actual rate of firing has some variance as it is dependent on the runloop.
From the documentation:
A timer is not a real-time mechanism; it fires only when one of the
run loop modes to which the timer has been added is running and able
to check if the timer’s firing time has passed.
To show this, I got rid of all of the rounding in your code and printed the output (you don't even need to pause to see this happen). Here is what this variance looked:
18.0004420280457
19.0005180239677
20.0004770159721
21.0005570054054
21.9997390508652
23.0003360509872
24.0003190040588
24.9993720054626
25.9991790056229
Sometimes it fires particularly late and this causes the whole thing to get thrown off. The rounding doesn't help because you are still depending on the timer for the actual reference time and eventually it will be off by more than a second.
There are a few ways to fix the situation here depending on what exactly you are trying to accomplish. If you absolutely need the actual time, you can adjust the timer to fire at fractions of a second and instead use that output to estimate the seconds a little more accurately. This is more work and will still not be totally right (there will always be a variance).
Based on your code, it seems like simply incrementing a number based on the timer should be enough to accomplish your goal. Here is a simple modification to your code making this work. This will count up simply and never skip a second in the count whether you pause or not:
/// The number of seconds that have elapsed since the initial fire.
private var elapsed = 0
private var timer: Timer?
/// Starts the shower timer.
func startTimer() {
elapsed = 0
// Fire the timer every second.
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
}
/// Pauses the shower timer.
func pauseTimer() {
// Pause the timer.
timer?.invalidate()
// Set the timer to `nil`, according to the documentation.
timer = nil
}
/// Resumes the timer.
func resumeTimer() {
// Recreate the timer.
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateElapsedSeconds), userInfo: nil, repeats: true)
}
/// Sets the number of elapsed seconds since the timer has been started, accounting for pauses, if any.
private func updateElapsedTime() {
// Get the time that has elapsed since the initial fire of the timer, and subtract any pauses.
elapsed += 1
// debug print
print(elapsed)
}
/// Updates the number of elapsed seconds since the timer has been firing.
#objc private func updateElapsedSeconds() {
// Configure the elapsed time with each fire.
updateElapsedTime()
// Post a notification when the timer fires, passing a dictionary that includes the number of elapsed seconds.
NotificationCenter.default.post(name: CustomNotification.showerTimerFiredNotification, object: nil, userInfo: nil)
}
In my main ViewController ViewDidLoad, I have the following code that calls the "tick" method every second.
self.timerTick = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(ViewController.tick), userInfo: nil, repeats: true)
In the "tick" method, I check if a boolean value is true every second, and if true, I need to call a second method called SpeakText(), but I want to call the SpeakText() only once every 10 seconds. How do I do this?
Create a class property to keep track of the time since the last SpeakText. Update it each time tick is called, and reset it to 0 when SpeakText is called:
var secondsSinceSpeakText = 10
func tick() {
secondsSinceSpeakText += 1
if boolFlag && secondsSinceSpeakText >= 10 {
SpeakText()
secondsSinceSpeakText = 0
}
}
Note: Starting secondsSinceSpeakText with an initial value of 10 will allow the first SpeakText to happen immediately.
Just create another timer for speaktext when and when you call your speak text function fire it. If its under 10 seconds return false
I have an NSTimerand I am using the method scheduledTimerWithTimeInterval but I would like the time interval it takes as a parameter to be a function, not an explicit value. How could I do this?
My code looks like this:
timer = NSTimer.scheduledTimerWithTimeInterval(time(), target: self, selector: "doStuff", userInfo: nil, repeats: true)
The compiler complains with the first parameter time()
edit: the error is "extra argument 'selector' in call"
I'm not sure exactly why you're getting that error, but time() is certainly not what you want for your time interval. That's supposed to be the number of seconds between firings of the timer. For something like a repeating animation timer that will typically be something like 1/30.0 or 1/60.0.
The time function (which, by the way, should take an argument) returns an integer that's the number of seconds since the "epoch." On Mac OS X the epoch is 12:00 AM, UTC of January 1, 2001.
Edit: It sounds from your comment like you've defined your own time function. I would first of all, change the name of your function to (say) timeInterval so it can't possibly collide with the system's time function. And I would make sure your function returns an NSTimeInterval, as opposed to a Float or some other number type, like:
func timeInterval() -> (NSTimeInterval) {
return 1/60.0
}
var timer = NSTimer.scheduledTimerWithTimeInterval(timeInterval(), target: self, selector: "doStuff", userInfo: nil, repeats: true)