NSTimer repeats getting blocked randomly? [duplicate] - ios

I have a UIScrollView that has a series of labels which are rapidly updating numbers (every .06 seconds). While the scroll view is moving, however, the NSTimer is paused and does not continue until after the scrolling and the elastic animation have finished.
How can I avoid this and have the NSTimer run regardless of the state of the scroll view?

An easy way to fix this is adding your NSTimer to the mainRunLoop.
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
To remove a timer from all run loop modes on which it is installed, send an invalidate message to the timer.

for swift:
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)

(Swift) An alternative: You can use a GCD-based timer system like this one:
class GCDTimer {
private var _timer : dispatch_source_t?
init() {
}
private func _createTheTimer(interval : Double, queue : dispatch_queue_t, block : (() -> Void)) -> dispatch_source_t
{
let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer != nil)
{
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC))), UInt64(interval * Double(NSEC_PER_SEC)), (1 * NSEC_PER_SEC) / 10);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
func start(interval : Double, block : (() -> Void))
{
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timer = _createTheTimer(interval, queue: queue, block: block)
}
func stop()
{
if (_timer != nil) {
dispatch_source_cancel(_timer!);
_timer = nil;
}
}
}

Related

Set a timer with variable intervals in swift

I need to set a specific timer asynchronously after executing an action like this:
calling my function (sending http request)
10 seconds after, sending another request
20 seconds after 2), sending another one
40 seconds after 3), another one
then send every 60 seconds another one
At any moment, I must be able to cancel my timer. Firstable I thought using DispatchQueue, but I see several post saying that it's not possible to cancel it.
Some post suggest to use DispatchWorkItem ( how to stop a dispatchQueue in swift ) but I'm not sur it fit my need (unless adding a sleep(10,20,40,60...) in each loop but will it not impact asynchronous part?).
Another answer from this post suggest to use Timer instead ( scheduledTimerWithTimeInterval ) with repeats:false, and invalidate it after each loop, but I didn't undertand how to do the loop in this case. Actually, here's my code, that just send a request after 10 seconds:
private func start() {
timer?.invalidate()
if(self.PCount > self.Intervals.count){
self.value = self.pollingIntervals.count-1
} else {
self.Value = self.Intervals[self.pCount]
}
print("set timer with \(pollingValue) as interval")
timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(pollingValue), repeats: false, block: { timer in
self.sessionManager.sendHit()
self.pollingCount+=1
})
}
The current goal is to do something like coroutine in Kotlin, like it work with this code :
private val Intervals = longArrayOf(10000,20000,40000,60000)
private var Count = 0
private fun start() {
currentJob = GlobalScope.launch {
while (true) {
delay(Intervals[if (Count > Intervals.size) Intervals.size - 1 else Count]) // 10,20,40 then every 60
session.sendHit()
pollingCount++
}
}
}
I'm not sure what solution is the most appropriate to my project
Here is a basic idea on how to approach the problem
struct RequestMananger {
var timers: [Timer] = []
mutating func startSequence() {
var delay = 10.0
sendRequest()
timers.append(scheduleTimer(delay))
delay += 20
timers.append(scheduleTimer(delay))
delay += 40
timers.append(scheduleTimer(delay))
delay += 60
timers.append(scheduleTimer(delay, repeats: true))
}
private func scheduleTimer(_ delay: TimeInterval, repeats: Bool = false) -> Timer {
return Timer.scheduledTimer(withTimeInterval: delay, repeats: false, block: { timer in
self.sendRequest()
})
}
func sendRequest() {
}
func cancelTimers() {
timers.forEach { $0.invalidate() }
}
}

Is `dispatch_source_cancel(timer)` sufficient to cancel recurring task?

I have a recurring task I want to run in my ios swift.
below is my code to repeat a task.
How can i stop the re-fresh task?
Am i missing something in the stopRefreshTimer()
Even if i don't miss anything, can it be that an old scheduled task will run even after I called stopRefreshTimer() ? Or is the halt immediate.
func startRefreshTimer() {
if (timer != nil) {return }
let queue = dispatch_queue_create("com.domain.app.timer", nil)
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC, 1 * NSEC_PER_SEC) // every 60 seconds, with leeway of 1 second
dispatch_source_set_event_handler(timer) {
if (self.timer == nil) {return}
self.getLineState(self.lineName)
}
dispatch_resume(timer)
}
func stopRefreshTimer() {
if (timer != nil)
{
dispatch_source_cancel(timer)
timer = nil
}
}

while loop that checks it's condition every second in swift

How does one create a while loop that checks it's condition every second?
maybe something like this:
while (isConditionSatisfied){
// wait for 1 second and than check again
}
EDIT: The system calls this function bannerViewDidLoadAd at random times. If it calls it at inappropriate time(condition is unsatisfied-my app is performing some other animation), I would like to defer its implementation(just an UIView animation) until the condition is satisfied(my app has finished animating, now the implementation should be executed). I was thinking I could check the condition in a while loop every second, but as you guys said..this is a bad idea.
Using while loop like this will create an infinite loop actually. You should use Timer().
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.loop), userInfo: nil, repeats: true)
And then
#objc
func loop() {
if yourCondition {
// your code here
timer.invalidate()
}
}
Make sure you declare timer with your other variable declarations, so it can be invalidated once your condition has been met:
var timer: Timer!
Swift loop with interval
iOS 10
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
if someCondition {
timer.invalidate()
}
}
*withTimeInterval - number of seconds
you can do using NSTimer like this one
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(yourMethodName) userInfo:nil repeats:YES];
I just put together a Swift NSObject extension that would allow you to do this. It uses a delay function to allow you to wait until certain conditions are met or you cancel it.
https://gist.github.com/d3signerd/795a9dced39cbee056010d5629d9ca06
// Wait Example
waitWhile( { [weak self] () -> condition_parameters in
if let weakSelf = self {
return ( conditionMet: `criteria to meet`, cancel: false )
}
else {
return ( conditionMet: false, cancel: true )
}
}, completion: { [weak self] in
// Wait completion code goes here...
})
// Delay Example
private var cancelableDelayClosure:dispatch_cancelable_closure? = nil
cancelableDelayClosure = delay( 1.0, closure: { () -> () in
// Delay completion code here...
})
cancelDelay( &cancelableDelayClosure )
updated code for swift,
let timer = Timer.scheduledTimer(
timeInterval: 1.0, target: self, selector: #selector(Your_current_vc.yourTask),
userInfo: nil, repeats: true)
func yourTask() {
}

dispatch_after equivalent in NSOperationQueue

I'm moving my code from regular GCD to NSOperationQueue because I need some of the functionality. A lot of my code relies on dispatch_after in order to work properly. Is there a way to do something similar with an NSOperation?
This is some of my code that needs to be converted to NSOperation. If you could provide an example of converting it using this code, that would be great.
dispatch_queue_t queue = dispatch_queue_create("com.cue.MainFade", NULL);
dispatch_time_t mainPopTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeRun * NSEC_PER_SEC));
dispatch_after(mainPopTime, queue, ^(void){
if(dFade !=nil){
double incriment = ([dFade volume] / [self fadeOut])/10; //incriment per .1 seconds.
[self doDelayFadeOut:incriment with:dFade on:dispatch_queue_create("com.cue.MainFade", 0)];
}
});
NSOperationQueue doesn't have any timing mechanism in it. If you need to set up a delay like this and then execute an operation, you'll want to schedule the NSOperation from the dispatch_after in order to handle both the delay and making the final code an NSOperation.
NSOperation is designed to handle more-or-less batch operations. The use case is slightly different from GCD, and in fact uses GCD on platforms with GCD.
If the problem you are trying to solve is to get a cancelable timer notification, I'd suggest using NSTimer and invalidating it if you need to cancel it. Then, in response to the timer, you can execute your code, or use a dispatch queue or NSOperationQueue.
You can keep using dispatch_after() with a global queue, then schedule the operation on your operation queue. Blocks passed to dispatch_after() don't execute after the specified time, they are simply scheduled after that time.
Something like:
dispatch_after
(
mainPopTime,
dispatch_get_main_queue(),
^ {
[myOperationQueue addOperation:theOperationObject];
}
);
Seven years late, but iOS 7 introduced this functionality to OperationQueue.
https://developer.apple.com/documentation/foundation/operationqueue/3329365-schedule
You could make an NSOperation that performs a sleep: MYDelayOperation. Then add it as a dependency for your real work operation.
#interface MYDelayOperation : NSOperation
...
- (void)main
{
[NSThread sleepForTimeInterval:delay]; // delay is passed in constructor
}
Usage:
NSOperation *theOperationObject = ...
MYDelayOperation *delayOp = [[MYDelayOperation alloc] initWithDelay:5];
[theOperationObject addDependency:delayOp];
[myOperationQueue addOperations:#[ delayOp, theOperationObject] waitUntilFinished:NO];
[operationQueue performSelector:#selector(addOperation:)
withObject:operation
afterDelay:delay];
Swift 4:
DispatchQueue.global().asyncAfter(deadline: .now() + 10 { [weak self] in
guard let `self` = self else {return}
self. myOperationQueue.addOperation {
//...code...
}
}
I used following code to get delayed call of operation:
class DelayedBlockOperation: Operation {
private let deadline: DispatchTime
private let block: (() -> Void)?
private let queue: DispatchQueue
override var isAsynchronous: Bool { true }
override var isExecuting: Bool {
get { _executing }
set {
willChangeValue(forKey: "isExecuting")
_executing = newValue
didChangeValue(forKey: "isExecuting")
}
}
private var _executing: Bool = false
override var isFinished: Bool {
get { _finished }
set {
willChangeValue(forKey: "isFinished")
_finished = newValue
didChangeValue(forKey: "isFinished")
}
}
private var _finished: Bool = false
init(deadline: DispatchTime,
queue: DispatchQueue = .global(),
_ block: #escaping () -> Void = { }) {
self.deadline = deadline
self.queue = queue
self.block = block
}
override func start() {
queue.asyncAfter(deadline: deadline) {
guard !self.isCancelled else {
self.isFinished = true
return
}
guard let block = self.block else {
self.isFinished = true
return
}
self.isExecuting = true
block()
self.isFinished = true
}
}
}
I was inspired by the gist

UIScrollView pauses NSTimer while scrolling

I have a UIScrollView that has a series of labels which are rapidly updating numbers (every .06 seconds). While the scroll view is moving, however, the NSTimer is paused and does not continue until after the scrolling and the elastic animation have finished.
How can I avoid this and have the NSTimer run regardless of the state of the scroll view?
An easy way to fix this is adding your NSTimer to the mainRunLoop.
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
To remove a timer from all run loop modes on which it is installed, send an invalidate message to the timer.
for swift:
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
(Swift) An alternative: You can use a GCD-based timer system like this one:
class GCDTimer {
private var _timer : dispatch_source_t?
init() {
}
private func _createTheTimer(interval : Double, queue : dispatch_queue_t, block : (() -> Void)) -> dispatch_source_t
{
let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer != nil)
{
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, Int64(interval * Double(NSEC_PER_SEC))), UInt64(interval * Double(NSEC_PER_SEC)), (1 * NSEC_PER_SEC) / 10);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
func start(interval : Double, block : (() -> Void))
{
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timer = _createTheTimer(interval, queue: queue, block: block)
}
func stop()
{
if (_timer != nil) {
dispatch_source_cancel(_timer!);
_timer = nil;
}
}
}

Resources