Before I figured this out I was trying GCD (Grand Central Dispatch) as that’s what everybody suggests on most popular forums, but whenever I tried using GCD I couldn’t find a solution that worked for this scenario.
I’m sure a more experienced Swift programmer will tell me there’s a way, but I’m here to provide a working solution that fit my needs… And hopefully helps somebody else out too!
My scenario was this:
- Make an API call to get some data
- Provide reminder at set time intervals until stopped by the user or the data is not available
I started with making an API call on every iteration and using GCD to execute that code using asyncAfter on a background queue. This worked fine until I remembered that currently I was the only person submitting API requests, and that if more users were doing the same I would quickly reach my rate limit allowance.
So a new solution was needed, and knowing that the data I was collecting wasn’t likely to change within 30–60 seconds; I had some wiggle room to loop and provide the same feedback to the user, while reducing my API calls by a factor of 3. Nobody would even know!
The scenario became:
- Make an API call
- Provide reminders for multiple loops of the timer without any API call
- Repeat until stopped by user or data isn’t available
This is where GCD stopped working for me.
I tried a for loop with asyncAfter, but that won’t work because asyncAfter returns immediately meaning that all of the iterations are fired almost immediately and then return almost at the exact same time. Not what we need!
I tried different GCD methods. I even tried sleep() even though I knew it was going to block my UI updates.
Everything I tried either fired all iterations at the same time, or blocked the main thread and left the UI un-usable…
Timer to the rescue!
I’ve explained my woes enough, so how do we fix it?.. Timer!
This Timer will loop every 10 seconds, and will increment the variable count when it does.
If count equals 2, then we fire off an API call, and then invalidate the Timer. I’ve bolded invalidate the Timer because it’s important. It will keep looping until you invalidate it and you can end up with multiple Timers if you call the entry to the loop again in your code.
So how does this work on an endless (but interrupt-able) loop? We can do something like this:
- When the view appears, call the function that contains the API call.
- If the API call is successful; call the function containing the Timer and do whatever else you need to do with the data.
- Let the Timer loop until it meets it’s condition providing feedback on each loop as if there has been another API call, invalidate it, and then call the function with the API call again.
After this I added the ability to pause the Haptic feedback with a long press gesture on the UI. To do that you can create a boolean, e.g. shouldStopAPICalls and toggle it on the gesture, wrap the block of code the Timer executes with an if-else, if shouldStopAPICalls is false then execute the loop, else- invalidate the Timer.
You’ll need to call the function with the API call to start up the loop again, but you will have a working loop (with delays if you added that part) that can be paused without blocking your main thread!
Thanks for reading, good luck with your apps!