Multithreading - DispatchQueue asyncAfter delay in Swift - ios

I am using the following code to invoke a function periodically every second. The problem is delay is actually 1.1 seconds and gets drifted more and more eventually as can be seen in NSLogs (and it is visible in other parts of the code as well apart from NSLog). Am I doing it wrong, or should I be using timer?
private func updateTimeCode() {
NSLog("Updating time")
//Some more code that doesn't take time
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0) {
[weak self] in
self ? .updateTimeCode()
}
}
019-08-06 17:15:19.713234+0530 MyApp-Swift[8299:2685215] Updating time
2019-08-06 17:15:20.812652+0530 MyApp-Swift[8299:2685215] Updating time
2019-08-06 17:15:21.913188+0530 MyApp-Swift[8299:2685215] Updating time
2019-08-06 17:15:23.028814+0530 MyApp-Swift[8299:2685215] Updating time

It's because of the part that you say some more code not relevant taking time. They take time and time passes between each invocation of asyncAfter so basically .now() becomes something more than exactly 1 second ago.
Anyway, it's not a conventional way to achieve it. You need to use timer for that purpose. Here's a useful tutorial about how to use it. Timer in Swift

Related

How to check an element's existence after a loading animation in a UI Test? Swift

The problem is simple: How to check an element's existence after a loading animation in a UI Test? BUT without using static timeouts.
For example: If animation lasts 3 seconds ui test waits for 3 seconds, if animation lasts 10 seconds ui test waits for 10 seconds or is there another way around it?
I'm trying to wait as long as my api call lasts simply
You can tackle this in one of two two ways: 1) Wait for your element to exist or 2) Wait for the animation to disappear.
In order to wait for your element to exist, you'd use Apple's waitForExistence function with a long timeout on your target element. This returns a boolean so you can simply assert directly on it.
XCTAssertTrue(myElement.waitForExistence(timeout: 15.0)) // wait for 15 seconds maximum
In order to wait for your animation to disappear, you'd identify it, and extend XCUIElement with the following function, which I use extensively and therefore bundle into my XCToolbox Cocoapod. You'd then be able to check the exists property on your target element.
public func waitForDisappearance(timeout: TimeInterval = Waits.short.rawValue) -> Bool {
let expectation = XCTNSPredicateExpectation(predicate: NSPredicate(format: UIStatus.notExist.rawValue), object: self)
let result = XCTWaiter.wait(for: [expectation], timeout: timeout)
switch result {
case .completed:
return true
default:
return false
}
}
This code would look like the following:
_ = animationElement.waitForDisappearance(timeout: 15.0)
XCTAssertTrue(myElement.exists)
Neither solution is wrong. The first is less code and arguably cleaner, the second is more explicit and possibly more readable.

30 Minute Post Delay Not Working With Swift

How do I create my program that will only allow users to post every 30 minutes using swift? This delay time only works if the app is running, besides that it will not work or restart the time running every time the app launches. I need a way to have them wait for only 30 minutes. My code right now is:
DispatchQueue.global(qos: .background) .async {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1800)) {
UserDefaults.standard.set(false, forKey: "PostTimeLimit")
}
}
Any tips or solutions would be very helpful. Thanks!
Instead of storing whether or not the user is allowed to post, store their last post time and compare it to the current time.

Timer is not starting after explicitly telling it to [duplicate]

This question already has answers here:
Why would a `scheduledTimer` fire properly when setup outside a block, but not within a block?
(3 answers)
Swift Timer.scheduledTimer() doesn't work
(2 answers)
Closed 3 years ago.
This is slowly beginning to drive me insane because it doesn't make sense...
I'm using Swift and can't seem to get this timer to start no matter what I do.
I have an NSObject that's handling all my time-related things in my app, and in that NSObject I have a function that initializes a timer. It looks like this:
class Time: NSObject {
static let sharedInstance = Time()
private override init(){}
var roundTimer = Timer()
//This is called from my splash screen while the app is loading
func initializeTimers(){
//Initialize the round countdown timer
roundTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(updateRoundTimerCountdown)), userInfo: nil, repeats: true)
print("The round timer should have started")
}
#objc func updateRoundTimerCountdown(){print("Round Timer ran"); Time.roundTimerIsAt -= 1; print("Round Timer: \(Time.roundTimerIsAt)")}
}
From my splash screen where the app loads all the user data, I call Time.sharedInstance.initializeTimers() in order to start it. I get to the point of it printing "The round timer should have started" to the debugger (which happens after the line that should start the timer) but the selector isn't hit at all. The "Round Timer ran" isn't printed to the debugger nor does the timer appear to have started, so what am I doing wrong?
I appreciate any help, even if the answer is glaringly obvious lol :P I've spent too much time on this!
I actually finally got it working! I'm sorry...there would have been too much code if I posted my entire splash screen view controller and Time NSData, but it appears the issue was elsewhere. But maybe someone else may run into this so this may help.
On my splash screen, I perform 15 'app-loading steps' that include several steps that gather information from servers and such... those steps I progress through by using completion handlers to ensure that the data is actually collected.
It's after collecting some of this data that I call the Time.sharedInstance.initializeTimers() function. My call to the servers runs on a different thread other than the main, so apparently by calling this function after the completion handler runs, it's still on the other thread and these timers can't start on anything other than the main thread!?
So all I did was this:
DispatchQueue.main.async {Time.sharedInstance.initializeTimers()}
And it works now... (facepalm). Doesn't make a whole lot of sense to me but... it works :P

Timer Loops - not NSTimer - Swift

There are lots of questions about using NSTimer for timer functions but I am instead using a CocoaPod "Countdown Label" https://github.com/suzuki-0000/CountdownLabel
My question/problem specifically is how to trigger an action/notification once it is complete I don't believe this to be the same as the many "NSTimer" questions/tutorials but please correct me if I'm wrong!
I've read the Timer documentation here https://developer.apple.com/documentation/foundation/timer but, again, don't see how it would apply to a 'custom timer library' like this Cocoapod.
I am currently trying this....
func testTimer() {
timerLabel.countdownDelegate = self
timerLabel.start()
if self.timerLabel.isFinished {
print("timer it's finished")
} else {self.timerLabel.start()
}
}
testTimer() is then called when an IBAction is pressed to star the timer.
My logic/thinking was that this function starts the timer, then checks if it finished. If it isn't finished it starts/continues the timer (timerLabel.start) and checks again basically in a loop until it is finished and then prints "timer is finished" but this isn't working and I'm not sure why? (but builds fine)
I know I could just scrap the pod and follow a Timer() tutorial but I'm trying to learn/understand how this sort of 'internal notification' for non Apple libraries would work generally at the same time as solving this specific problem. I hope this all makes sense.
NB. Some of the questions/answers I've read through that I don't believe are what I need are How to check if a NSTimer is running or not in Swift?, Check if Timer is running, Perform segue when timer is finished but they all use the Apple "Timer()".
NB. I am setting up the timer is ViewDidLoad with data from a Segue like this :
let timerLabelTime = Int(selectedWorkoutTime)
timerLabel.setCountDownTime(minutes: Double((timerLabelTime)!*60))
timerLabel.countdownDelegate = self
timerLabel.pause()
which works fine

Execute a line of code after a certain amount of time in the background on iOS 7

To provide some context, the application I'm working on allows the user to record a series of events along a timeline. One important piece of feedback we received from beta testers was that when the user exits the app while recording they'd like the timer to keep up to date for a set amount of time.
For example if the user has recorded 5 minutes and then leaves the app to check an email for 20 seconds, the timer should be 5:20, but it was actually still 5 minutes.
So we've fixed that - the app now would now show 5:20, and this works in the background.
However we can't figure out a way to stop it after a certain amount of time. A user might want the timer to resume if the app is in the background for a short amount of time, but if you leave it in the background for 30 minutes, the timer will update, and I'd like to be able to give the users an optional cut-off time so that the app pauses after a set amount of time.
I've tried listening out for UIApplicationDelegate notifications, but the problem is I'm looking for a point between applicationDidEnterBackground: and applicationWillTerminate, but there's sadly nothing to cater for my needs there.
To summarise, I'd like to have a grace period of 30-60 seconds from applicationWillResignActive: to determine whether or not to pause the timer, or let it keep going.
Would this be possible?
Thanks in advance
It's generally a bad idea to assume your app will be running in the background.
A better way to think about it IMO would be to set a variable to the current time in applicationDidEnterBackground: and check the time interval to now in applicationWillBecomeActive: :
If the interval is bigger that your grace period, have the time backup to when the app was paused.
If it is smaller, update it as if the app was never in the background.
Use dispatch_after to execute a block of code after a certain number of second:
int64_t delay = 30.0; // In seconds
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0ull), ^(void){
//DO SOMETHING
});
Swift 3.0 Xcode 8.0 Code sample:
Using delegate methods : applicationWillBecomeActive & applicationWillResignActive to capture the time in between.
OR
let deadlineTime = DispatchTime.now() + .seconds(30)
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
alert.dismiss(animated: true, completion: nil)
}
Hope this helps.

Resources