Coalesce or otherwise fire methods "every so often"? - ios

The server that provides data to my app recently added a feature that allows you do to basic logging like "user selected logo" or "user is quitting".
The only place I'd like to use this is in a page with several sliders that does a calculation on the input values. This is continuous, it re-calculates the output as you move the sliders around.
Which leaves me the problem of when to call this logging method. I don't want to call it every time the numbers change, or I'll murder the server. I could put a "Calculate now" button, but that kills the entire mode-less UI I like.
Is there a way that I can coalesce calls so all the calls made within, say, 5 seconds, results in only one call to the work method? I'd also have to force the method to fire if the user does something else, like navigates away or quits the app.

You can easily add an NSTimer to the IBAction method you have for your slider. Every time that method is called, invalidate the timer and start it again. Put the analytics call in the timer's action method, which will only be called when the timer can actually complete.
For example:
#interface ViewController ()
#property (nonatomic) NSTimer *actionTimer;
#end
#implementation ViewController
- (IBAction)sliderChanged:(UISlider *)sender
{
[self.actionTimer invalidate];
NSLog(#"Slider value: %f", sender.value);
self.actionTimer = [NSTimer scheduledTimerWithTimeInterval:2
target:self
selector:#selector(timerCompleted)
userInfo:nil
repeats:NO];
}
- (void)timerCompleted
{
NSLog(#"Timer completed.");
}
#end

How about a background thread that observes the calculated value and fires an update by scheduling a block on a background thread when the output value is stable, like
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(showValue:)
name:#"showValue"
object:nil];
You can also schedule the block in viewWillDisappear for the leaving the view and (I think) quit events.

Related

iOS Objective C progress bar update in background

I've run various types of code to try and make a progress bar increment for x seconds and continue to do so even when in the background. Currently the bar just resumes from where it left off, via the timer, when going from background to focus.
How can I continue the timer even when the user minimizes the app?
Declare a NSTimer property in .h. Then call it from anywhere like :
self.nstimer= [NSTimer scheduledTimerWithTimeInterval:0.10 self selector:#selector(updateTime:) userInfo:nil repeats:YES];
And inside the method (audiocontroller is another class property):
- (void)updateTime:(NSTimer *)timer
{
// Progress bar value
progressView.progress = ((float)recievedData / (float) xpectedTotalSize);
}
It will continue call the method even the app is in background. You can cancel this method calling from applicationDidBecomeActive() method.

iOS Set up NSTimer to call a method only on active / inactive page

I one of my ViewController I want to call a method updateVisitotsLists on some time criteria's and am not able to decide which way will be best to achieve it.
1) Every time the view is loaded/appeared I want to call this method.
For this in viewDidAppear method I can call this before calling [super viewDidAppear];, so that works, I believe.
2) If user is on this view only, then I want to call this method after every 5 secs.
For this, I need to set a NSTimer. I want to stop this timer when viewDidDisappear - as don't want it running unnecessary. Should I use an UnScheduled Timer as shown here and start and stop in appear & disappear methods ? In viewDidAppear, initially I call this method, and then also set
NSTimer *t = [NSTimer scheduledTimerWithTimeInterval: 5.0
target: self
selector:#selector(updateVisitotsLists:)
userInfo: nil repeats:NO];
What will be the best way and methodology to achieve what I am looking for ? Any help is highly appreciated.
Thanks.
UPDATED :-
#Lord Zolt, as per your comment I did the following :-
//In .h
#property (strong, nonatomic) NSTimer *timer;
// .m
#synthesize timer;
- (void)viewDidLoad
{
........
[super viewDidLoad];
// CREATE TIMER
timer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:#selector(onTimerCall:) userInfo:nil repeats:YES];
}
-(void) viewWillDisappear:(BOOL)animated {
[timer invalidate];
timer = nil;
[super viewWillDisappear:animated];
}
-(void) onTimerCall: (NSTimer *) _timer {
// UPDATE VISITOR'S LIST
[self updateVisitotsLists];
}
Is this proper ?
I would recommend using timers.
Create an NSTimer property, but I would recommend calling invalidate on them on viewWillDisappear.
If you don't call invalidate, when the view controller is dismissed or popped, it won't be deallocated, since NSTimer will keep it alive.
The code you posted is fine with a few modifications:
You don't need to #synthesize properties anymore (unless you overwrite both the setter and getter).
Don't set timer to nil.
Edit: If you want the timer to be related to the screen (aka it should be executed only when the screen is visible), you should initialise in viewDid(Will)Appear and stop it in viewDid(Will)Disappear.

Smooth scrolling of text (Autoscroll) in UITextView iOS

I am working on a project where I there is some text in UItextview. The app wants to continuous smooth scroll that text and also wants to manage its scrolling speed. I mean here the text should scroll smoothly and the app contains slider where I can manage the speed.
Below is some sample code which I am using.
- (void) autoscrollTimerFired:(NSTimer *)timer {
[self.completeText setContentOffset:CGPointMake(0, self.completeText.contentOffset.y + 1.0) animated:NO];
if (self.completeText.contentOffset.y != self.completeText.contentSize.height - self.completeText.frame.size.height) {
scrollingTimer = [NSTimer scheduledTimerWithTimeInterval:velocityFactor target:self selector:#selector(autoscrollTimerFired:) userInfo:nil repeats:NO];
} else {
[scrollingTimer invalidate];
}
}
The Velocity factor is the number of seconds which ranges between 0.0 to 2.5.
It works nice in simulator but in device it moves with jerks or I must say like pausing at after some line.
Could you please suggest any solution here? All suggestions are welcome.
The NSTimers actually just periodically fire events into the enclosing NSRunLoop, which each thread has (or should have). So, if you have a child (or background) process running in a different thread, the NSTimers will fire against that thread's NSRunLoop instead of the application's main NSRunLoop.
NSTimer events fire on the thread where you scheduled the timer. If you scheduled the timer on the main run loop, then the timer will fire on the main thread and be “safe and synced” with input events.
So I have some suggestions for try that (I am not sure how much it will be successful in your case)
Don't use NSTimer. Try to call from main thread via selector with "afterDelay". As given in code
[self performSelector:#selector(autoScroll) withObject:nil afterDelay:1.0];
Use KVO or may be NSNotification for event triggering. (Not do it directly ,I Think better approach.)
I prefers KVO , so writing here the steps to use it :-
Make a class with a variable of NSNumber (which should accessible outside the class). Make a object of that class and add a Observer on it. (here model is object of that class).
[model addObserver:self forKeyPath:#"name" options:(NSKeyValueObservingOptionOld |
NSKeyValueObservingOptionNew) context:nil];
Implements it's delegate methods.
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
// Do here what ever you want to do. It will call every time whenever there will be any change in that class variable.
}
Make a method in controller "autoScroll" which called via afterDelay selector. And change the value of the NSNumber vairabe value (any logic incremental way. Doesn't affect a lot).
Hope this helps you !!! Try This ...best of luck

Appropriate way to call and cancel a selector in Objective-c?

So I have an app that when a user touches a certain object, I kick-off a selector via delay. I am not sure I want or need the delay, but am not sure of best practice, maybe a queue? Anyway, here is what I need, regardless of what I have now.
WHAT I HAVE NOW
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(doSomething) object:self];
[self performSelector:#selector(doSomething) withObject:nil afterDelay:2.0];
When the user touches a certain object I need to kick-off a method, but if he/she touches the object again, I want to not call the method.
Use case #1:
User touches object
User does nothing for 2 seconds
Call selector
Use case #2:
User touches object then
User touches object .5 seconds later (so cancel selector call)
User touches object .3 seconds later (so cancel selector call)
User touches object .9 seconds later (so cancel selector call)
User doesn't touch anything for 2 seconds
Call selector
If feel like performSelector and cancelPrevious are hacky. Should I be using some sort of queue and then clearing out the queue every time the user touches again?
Or should I use a timer and just restart the timer each time the user touches it?
I wrote something quick, hopefully it'll help. Every time start is hit the timer resets
#interface ViewController ()
{
NSTimer *timer;
NSInteger seconds;
}
#end
- (IBAction)start:(id)sender
{
seconds = 5;
[timer invalidate];
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(execute) userInfo:nil repeats:YES];
}
- (void)execute
{
if(seconds > 0) {
NSLog(#"seconds: %li", (long)seconds);
seconds--;
}
else {
NSLog(#"fire");
[timer invalidate];
}
}

iOS reading data from server regularly

I need to create a iOS app where the app has to continuously check for the updates from the server(may be every 30 secs). But only when the app is running on the foreground.
I know this will drain the battery, but this will run on a environment where there's no internet. So we can't use push notifications.
Only option I can think of is sending a request to the server every 30 secs or so and get the updates. What is the best way to do this? Using NSTimer and NSURLConnection or any other better approaches?
Also if I use a timer, when the app goes to the background will it pause and will it start running as it comes to the foreground again? Is there a chance that app get killed while its on background?
Thanks
Using NSTimer and NSURLConnection or any other better approaches?
My first thought was also to use NSTimer and NSURLConnection.
Also if I use a timer, when the app goes to the background will it pause and will it start running as it comes to the foreground again?
Yes, it will. It doesn't exactly pause, but based on my testing in the simulator, the effect is similar. Let's say the timer is set to go off at 00:00:00, 00:00:30, 00:00:60, ... and you background the app at 00:00:15 and resume it at 00:00:45. The timer that was supposed to fire at 00:00:30 fires immediately when you resume (at 00:00:45), and the next firing (at 00:00:60) and subsequent firings are back on schedule.
Is there a chance that app get killed while its on background?
Yes, there is. But if you start the timer whenever the app launches, this shouldn't be a problem, right?
Your best bet is to setup a separate object that manages these operations on a background thread. Then in your app delegate, when
- (void)applicationWillResignActive:(UIApplication *)application
is called, have this special object stop all of it's synchronizing and clean up anything it needs to.
Then when:
- (void)applicationDidBecomeActive:(UIApplication *)application
gets called as the app gets active again, signal your object to query / poll on its background thread again.
Your custom object could have an interface like this
#interface PollingObject : NSObject
{
NSTimer* _timer;
NSUinteger _interval;
BOOL _cancel;
BOOL _isPolling;
dispatch_queue_t _pollQueue;
}
- (void)startPolling;
- (void)stopPolling;
#end
The implementation can be something like this:
#implementation PollingObject : NSObject
- (id)init
{
if (self = [super init])
{
_interval = 1; // 1 second interval
_cancel = NO; // default to NO
_isPolling = NO; // default to NO
// init your background queue
_pollQueue = dispatch_queue_create("com.yourconame.yourappname.pollQueue", NULL);
}
return self;
}
- (void)heartbeat
{
if (_cancel)
{
// stop the timer
[_timer invalidate];
_isPolling = NO;
return;
}
// Runs the polling method ONCE on a background queue
dispatch_async(_pollQueue, ^{
[self pollingMethod];
});
}
- (void)pollingMethod
{
// Do actual network polling work here...but only run it once. (don't loop)
}
- (void)startPolling
{
_cancel = NO;
if (_isPolling)
{
NSLog(#"Already polling");
return;
}
// schedule the method heartbeat to run every second
_timer = [NSTimer scheduledTimerWithTimeInterval:_interval target:self selector:#selector(heartbeat) userInfo:nil repeats:YES];
}
- (void)stopPolling
{
// we set the flag here and the next second the heartbeat will stop the timer
_cancel = YES;
}
#end
Look at Rocket real-time networking which looks easy to setup through AFNetworking 2.0.
https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-2.0-Migration-Guide
See the last part of this wiki. I have not used it but it would be something I would try if I had your requirements.

Resources