Using dispatchQueue in loops - swift

I am attempting to loop something with a delay (just as a proof of concept) this is for something else. But to simplify it, here is an example.
so in this example, I have a string with "text" and I want to loop the addition of another string lets say 10 times. The only thing is that I want there to be a delay in each iteration. here is my code thus far.
// global variable
var myString = "text"
// an action inside a button
let delayInSeconds = 1.0
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) {
for _ in 1...10 {
self.myString += "another text"
}
}
labelOne.text = myString
}
I should add that the result is that all 10 "another text" are added immediately without any delay.
thank you

In your example, you append your string ten times in the same work unit. Try dispatching work once per loop:
for delay in 1...10 {
let delayInSeconds = Double(delay)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) {
self.myString += "another text"
labelOne.text = myString
}
}
This loop won't work well for arbitrarily large values, though. It also doesn't provide the kind of precise timing we might want for user interface changes. For that, we can use Timer. Here’s the same problem reworked with Timer:
// Add to the class body…
var i = 1
// Lazy initialization so the timer isn’t initialized until we call it
lazy var timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) {timer in
guard self.i <= 20 else {
timer.invalidate()
return
}
self.label.text?.append(" another text")
self.i += 1
}
// Add to the button action…
timer.fire()

If I understand correctly, you want to show the text one by one with delay for each “text”.
You can use recursion here.
1.Move the dispatch code into a method.
2. Remove for loop.
3. Do your necessary actions inside dispatch it.
4.call that same method again inside dispatch.
Do remember to use a counter variable that increments each time method is called. Use it to break the recursion.

Related

Calling function inside closure, Swift

private func showOrder() {
disableButtons()
var count = 0
_ = Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { time in
self.popColor(color: self.colorOrder[count])
count += 1
if count == self.colorOrder.count {
time.invalidate()
self.depopAllButtons()
self.enableButtons()
}
}
}
In this function I am looping through an Array of colors. The popColor() method is used to add shadows and such to a button the screen.
The issue that I am having is that the popColor() function seems to execute after the closure has executed, so it is always one behind where it should be. The error this causes is on the last iteration of the loop the last color doesn't execute its popColor().
Is it possible to call a function inside of a closure as I am doing here with popColor()?
I have write your code with small changes:
var count = 0
_ = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { time in
debugPrint("set color")
count += 1
if count == 3 {
time.invalidate()
debugPrint("disable set color")
}
}
And the console printed:
"set color"
"set color"
"set color"
"disable set color"
Your timer is working correctly. Look for a problem elsewhere.

Having trouble with a while loop in Swift 5

Essentially, I'm making an app about a virtual dog to help people take care of their dogs. One of the screens gives you fifteen seconds to pet the dog five times. Whenever I try to load the screen, the app freezes. The code is inside of viewDidLoad()
while timesPetted < 5 {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1), execute: {
timer += 1
if timer == 15 {
self.failureLabel.isHidden = false
self.reset.isHidden = false
self.timesPetted = 0
}
})
}
When I delete the while loop, the screen loads normally and runs perfectly, but (obviously) there isn't a time limit. If I move the while loop out of viewDidLoad(), I get an error saying that Xcode "Expected declaration".
Either use a timer that is set to expire in 15 seconds
let timer = Timer.scheduledTimer(withTimeInterval: 15.0, repeats: false) { timer in
self.failureLabel.isHidden = false
self.reset.isHidden = false
self.timesPetted = 0
}
Or if you want to use DispatchQueue then use it only once
DispatchQueue.main.asyncAfter(deadline: .now() + 15) {
self.failureLabel.isHidden = false
self.reset.isHidden = false
self.timesPetted = 0
}
it appears the while loop is running infinitely due to the value of timesPetted. As the value of timesPetted is not changing at all once it enters the while loop.
To solve your issue you can make changes to your code as below :-
You must be updating your timesPetted value some where in the code.
lets say timesPetted is changed in function called "Petting", so when this function is called have check, which limits the user to pet till 5 times only and another check for 15 seconds. As shown below.
func petting() {
if reset.isHidden && timesPetted <= 5{ // here the reset.isHidden will become false in DispatchQueue (present in viewDidLoad) once the 15 seconds have been passed.
timesPetted += 1
}
}
And also make sure to add this line of code in your viewDidLoad.
DispatchQueue.main.asyncAfter(deadline: .now() + 15) {
self.failureLabel.isHidden = false
self.reset.isHidden = false
self.timesPetted = 0
}

Timer initialised in 'for in' loop firing twice for each loop

I'm building an application that displays a custom driving route on a map using MapKit. It takes an array of coordinates and goes through a for loop, in each loop the corresponding coordinates (i.e [0] and [1] or [7] and [8]) are assembled into an individual request and then drawn on the map.
In order to bypass MapKit's throttling error, I have a timer that is set so that each request is spaced out 1 second apart.
My issue is that the timer is firing twice for each individual request, which is resulting in double the number of necessary requests being made.
I'm using Xcode 10 and Swift 4, this is the function where I believe the issue is occurring.
func requestDirections(arrays coordinates: [CLLocationCoordinate2D]) {
var dest = 1
let coordinatesArray = coordinates
let end = coordinatesArray.count - 2
var timerDelay:Double = 1
for origin in 0...end {
if dest <= coordinatesArray.count {
let startCoord = MKPlacemark(coordinate: coordinatesArray[origin])
let destCoord = MKPlacemark(coordinate: coordinatesArray[dest])
let request = MKDirections.Request()
request.source = MKMapItem(placemark: startCoord)
request.destination = MKMapItem(placemark: destCoord)
request.transportType = .automobile
request.requestsAlternateRoutes = false
print("Starting timer for \(dest) at \(timerDelay) seconds")
Timer.scheduledTimer(withTimeInterval: timerDelay, repeats: false) { timer in
self.createIndividualDirectionsRequest(request)
print("Timer fired")
}
dest = dest + 1
timerDelay = timerDelay + 1
}
}
}
I'm expecting the timer to fire once for each loop, if this is happening the expected console output would be
"Starting timer for 'dest' at 'timerDelay' seconds" printed 18 times (or whatever the size of the array is)
"Timer fired" being printed 18 times as well
While "Starting timer for 'dest' at 'timerDelay' seconds" is in fact being printed the correct number of times, "Timer fired" is being printed twice as often as it should.
Thank you very much for your help and your patience, I am quite new to programming and am struggling to wrap my head around this issue.
Actually I do not know why "Timer fired" is printed twice for each individual request :).
But if you are using Timer.scheduledTimer just to delay the execution of the block, you can use DispatchQueue.main.asyncAfter instead of Timer
DispatchQueue.main.asyncAfter(deadline: .now() + timerDelay) {
self.createIndividualDirectionsRequest(request)
print("Timer fired")
}
You can also use DispatchQueue.global.asyncAfter but I think you want to execute the block in main ui thread

Swift for iOS - 2 for loops run at the same time?

I have two objects where I need to update their UI at the same time. I have a for loop for one, and after that another for loop. Each iteration in the for loop I have a short delay so that for elements in the object I am making a UI change... one after the other - not seemingly all at once.
func update(value: Int){
var delay: Double = 0.05
// first loop
for i in 0...value {
delayWithSeconds(delay) {
//do something with object 1
}
delay = delay + 0.05
}
var delay2: Double = 0.05
// second loop
for i in 0...value {
delayWithSeconds(delay2) {
//do something with object 2
}
delay2 = delay2 + 0.05
}
}
// Utility
func delayWithSeconds(_ seconds: Double, completion: #escaping () -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
completion()
}
}
I have tried wrapping each for loop with DispatchQueue.main.async and it didn't make a difference. In short - I would like to run both for loops at the same time (or perceived as such). These are on the UI thread.
I tried this and it seemed to work out quite well. It does exactly what I want it to do (at least visually they seem to run at the same time).
let concurrentQueue = DispatchQueue(label: "net.ericd.hello", attributes: .concurrent)
concurrentQueue.async {
//my loop with delay here for object 1.
}
concurrentQueue.async {
//my separate loop with delay here for object 2.
}
We can use it when we want execute different arrays at the same time:
using this Generic Function
zip(_:_:)
Here i took 2 array:
var arrOfInt = ["1","2","3"]
var arrOfIntString = ["one","two","three"]
for (intNum, intString) in zip(arrOfInt, arrOfIntString) {
print("Int:\(intNum), String:\(intString)")
}

Testing a Timer in Xcode with XCTest

I have a function that does not need to be called any more than every 10 secs. Every time I invoke the function, I reset the timer to 10 secs.
class MyClass {
var timer:Timer?
func resetTimer() {
self.timer?.invalidate()
self.timer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: false) {
(timer) -> Void in
self.performAction()
}
}
func performAction() {
// perform action, then
self.resetTimer()
}
}
I would like to test that calling performAction() manually resets the timer to 10 secs, but I can't seem to find any good way to do it. Stubbing resetTimer() feels like the test wouldn't really be telling me enough about the functionality. Am I missing something?
XCTest:
func testTimerResets() {
let myObject = MyClass()
myObject.resetTimer()
myObject.performAction()
// Test that my timer has been reset.
}
Thanks!
If you want to wait for the timer to fire, you'll still need to use expectations (or Xcode 9's new asynchronous testing API).
The question is what precisely you're trying to test. You presumably don't want to just test that the timer fired, but rather you want to test what the timer's handler is actually doing. (Presumably you have a timer in order to perform something meaningful, so that's what we should be testing.)
WWDC 2017 video Engineering for Testability offers a nice framework to be thinking about how to design code for unit tests , which need:
control over inputs;
visibility to outputs; and
no hidden state.
So, what are the inputs to your test? And, more importantly, what is the output. What assertions do you want to test for in your unit test?
The video also shows a few practical examples of how one might refactor code to achieve this structure through judicious use of:
protocols and parameterization; and
separating logic and effects.
It's hard to advise further without knowing what the timer is actually doing. Perhaps you can edit your question and clarify.
Good that you found a solution, but answering the question in title;
To test if timer actually works (i.e. runs and calls callback), we can do something like:
import XCTest
#testable import MyApp
class MyClassTest: XCTestCase {
func testStartTimer_shouldTriggerCallbackOnTime() throws {
let exp = expectation(description: "Wait for timer to complete")
// Dummy.
let instance: MyClass! = MyClass()
instance.delay = 2000; // Mili-sec equal 2 seconds.
instance.callback = { _ in
exp.fulfill();
}
// Actual test.
instance.startTimer();
// With pause till completed (sleeps 5 seconds maximum,
// else resumes as soon as "exp.fulfill()" is called).
if XCTWaiter.wait(for: [exp], timeout: 5.0) != .completed {
XCTFail("Timer didn't finish in time.")
}
}
}
When having a class like:
public class MyClass {
public var delay: Int = 0;
public var callback: ((timer: Timer) -> Void)?
public func startTimer() {
let myTimer = Timer(timeInterval: Double(self.delay) / 1000.0, repeats: false) {
[weak self] timer in
guard let that = self else {
return
}
that.callback?(timer)
}
RunLoop.main.add(myTimer, forMode: .common)
}
}
First, I would say I don't know how your object was working when you don't any member called refreshTimer.
class MyClass {
private var timer:Timer?
public var starting:Int = -1 // to keep track of starting time of execution
public var ending:Int = -1 // to keep track of ending time
init() {}
func invoke() {
// timer would be executed every 10s
timer = Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(performAction), userInfo: nil, repeats: true)
starting = getSeconds()
print("time init:: \(starting) second")
}
#objc func performAction() {
print("performing action ... ")
/*
say that the starting time was 55s, after 10s, we would get 05 seconds, which is correct. However for testing purpose if we get a number from 1 to 9 we'll add 60s. This analogy works because ending depends on starting time
*/
ending = (1...9).contains(getSeconds()) ? getSeconds() + 60 : getSeconds()
print("time end:: \(ending) seconds")
resetTimer()
}
private func resetTimer() {
print("timer is been reseted")
timer?.invalidate()
invoke()
}
private func getSeconds()-> Int {
let seconds = Calendar.current.component(.second, from: Date())
return seconds
}
public func fullStop() {
print("Full Stop here")
timer?.invalidate()
}
}
Testing (explanation in the comments)
let testObj = MyClass()
// at init both starting && ending should be -1
XCTAssertEqual(testObj.starting, -1)
XCTAssertEqual(testObj.ending, -1)
testObj.invoke()
// after invoking, the first member to be changed is starting
let startTime = testObj.starting
XCTAssertNotEqual(startTime, -1)
/*
- at first run, ending is still -1
- let's for wait 10 seconds
- you should use async method, XCTWaiter and expectation here
- this is just to give you a perspective or way of structuring your solution
*/
DispatchQueue.main.asyncAfter(deadline: .now() + 10 ) {
let startTimeCopy = startTime
let endingTime = testObj.ending
XCTAssertNotEqual(endingTime, -1)
// take the difference between start and end
let diff = endingTime - startTime
print("diff \(diff)")
// no matter the time, diff should be 10
XCTAssertEqual(diff, 10)
testObj.fullStop()
}
this is not the best of way of doing it, however it gives you view or a flow on how you should achieve this :)
I ended up storing the original Timer's fireDate, then checking to see that after the action was performed the new fireDate was set to something later than the original fireDate.
func testTimerResets() {
let myObject = MyClass()
myObject.resetTimer()
let oldFireDate = myObject.timer!.fireDate
myObject.performAction()
// If timer did not reset, these will be equal
XCTAssertGreaterThan(myObject.timer!.fireDate, oldFireDate)
}