So to initiate an NSTimer we do this:
timer = [NSTimer scheduledTimerWithTimeInterval:0.35 target:self selector:#selector(timerMethod) userInfo:nil repeats:YES];
But what if I want this to run every 0.3 - 0.7 seconds (randomly). I can't do arc4random because then it would choose a number and stick to it.
The only way I've thought of is invalidating it every time it run the 'timerMethod' and then setting a new random time for it but I'm concerned that that will have an effect on the performance.
Is there another way, better to do this?
Instead of using a timer, use a series of [self performSelector:#selector(timerMethod) withObject:nil afterDelay:<random value>] calls. It'll look roughly like tail recursion — timerMethod will routinely schedule a future call to itself somewhere within it.
Also be mindful of retain cycles. Both NSTimer and performSelector:... retain their targets. You could either decline to use either and instead use dispatch_after having captured only a weak reference, or use an approximate two-stage deallocation where a non-dealloc call explicitly invalidates the timer or sets a flag to tell you not to schedule another call to timerMethod.
The GCD solution would look like:
- (void)timerMethod
{
// schedule the next call to timerMethod, keeping only a weak
// reference to self so as not to extend the lifecycle
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(<random value> * NSEC_PER_SEC));
__weak typeof(self) weakSelf = self;
dispatch_after(popTime, dispatch_get_main_queue(),
^{
[weakSelf timerMethod];
});
// ... and do whatever else here ...
}
If you avoid the convenience method and use the NSTimer init method, you get a chance to setTolerance
Another alternative is to create a makeTimer method. In it invalidate the timer if it is not nil. Create a new timer using the init method and set a random interval.
Use the makeTimer method to make your timer.
Related
I have a number of methods that insert data into database table.These methods are called one after the other. While the insertion process in on, I want my UI to show appropriate message.
eg-
[dbManager insertIntoTable_ZADDED_FOOD_ITEMS_WithData:[resultObj objectForKey:#"food"]];
[dbManager insertIntoTable_ZUSDA_FOODS_WithData:[resultObj objectForKey:#"usda_food"]]
[dbManager insertIntoTable_ZDAILY_MENU_RECIPE_WithData:[resultObj objectForKey:#"daily_menu_recipe"]];
So, when I say
[dbManager insertIntoTable_ZADDED_FOOD_ITEMS_WithData:[resultObj objectForKey:#"food"]];
label on ui must show 'Updating Added Food Items...'
then when next line comes
[dbManager insertIntoTable_ZUSDA_FOODS_WithData:[resultObj objectForKey:#"usda_food"]];
label should read as 'Updating Usda Food' and so on.
I want the text on label to remain at least for a second. I have used 'dispatch_after' but still not able to get the desired effect.
Is there a way to insert delays between method calls and also to make sure that second method is called only when first one is completed.
Use the Command Pattern its a well known design pattern, basically its a list and each item has state, when one finishes the next one starts.
this case will allow you to not have a completion block inside a completion block inside a completion block.
You can make use of completion block :)
change your method to take a completion block as an arguement and once done with the job execute the completion block :) and in completion block start the next method call :)
You can modify your method signature as follow
-(void)insertIntoTable_ZADDED_FOOD_ITEMS_WithData:(NSData *)data andCompletionBlock:(void (^)())completionBlock{
//do whatever you want to do here
//once done simply say
completionBlock();
}
and you can call this method as :)
[dbManager insertIntoTable_ZADDED_FOOD_ITEMS_WithData:[resultObj objectForKey:#"food"] andCompletionBlock:^{
//call next method
//[dbManager insertIntoTable_ZUSDA_FOODS_WithData:[resultObj objectForKey:#"usda_food"] andCompletionBlock:^{
}];
}];
And in case you dont wanna send any completion block simply pass nil :)
But in that case dont forget to add the check as if(completionBlock) before calling completionBlock() app will crash in case it is passed as nil :)
on a second thought :)
if your method insertIntoTable_ZADDED_FOOD_ITEMS_WithData: is synchronous :) you can change the label directly like
yourLabel.text = #"Updating Added Food Items...";
[dbManager insertIntoTable_ZADDED_FOOD_ITEMS_WithData:[resultObj objectForKey:#"food"]];
yourLabel.text = #"Doing something else ...";
[dbManager insertIntoTable_ZUSDA_FOODS_WithData:[resultObj objectForKey:#"usda_food"]];
yourLabel.text = #"Doing something else again...";
[dbManager insertIntoTable_ZDAILY_MENU_RECIPE_WithData:[resultObj objectForKey:#"daily_menu_recipe"]];
No need of any delay or completion block here buddy :)
Finally if your concern is methods are executing much faster and label wont even appear for a fraction of a second and you intentionally want to add a delay between each method :)
-(void)insertIntoTable_ZADDED_FOOD_ITEMS_WithData:(NSData *)data andCompletionBlock:(void (^)())completionBlock{
//do whatever you want to do here
//once done simply say
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
completionBlock();
});
}
Hope my answer helped you :) Happy coding :)
This will be helpful for what you required.I did this successfully.
- (void)button_circleBusy:(id)sender {
firstButton.enabled = NO;
// 60 milliseconds is .06 seconds
[NSTimer scheduledTimerWithTimeInterval:.06 target:self selector:#selector(goToSecondButton:) userInfo:nil repeats:NO];
}
- (void)goToSecondButton:(id)sender {
firstButton.enabled = YES;
secondButton.enabled = NO;
[NSTimer scheduledTimerWithTimeInterval:.06 target:self selector:#selector(goToThirdButton:) userInfo:nil repeats:NO];
}
I hope it will work for you also!!
When you are not running you function in background then it should call one by one (after first finish). You don't need to bother about completion of first then start next.
And if you want to show message notification to hold for a sec on your screen then remove label with delay about a sec.
You can also delay your method call for a sec and so.
[self performSelector:#selector(removeMessageLabel) withObject:nil afterDelay:1.0];
Here "afterDelay" is responsible to make a pause for that particular method which is declared inside but compiler will move for further line execution.
I cannot seem to work this one out. Here is my set up:
I have a function called requestDataWithCompletion:(someBlock)block. I call it when the class is initialised. The function requests certain motion data. I want to do this periodically, therefore, the first time I call this function, I specify some completion code which sets up a timer that re-calls this function periodically. The timer calls it via another function requestDataWithoutCompletion which simply calls the requestDataWithCompletion but with an empty block (so I don't keep creating timers);
- (void) requestDataWithCompletion:(someBlock)block {
// BREAK POINT 1
[self.motionManager queryActivityStartingFromDate:start toDate:[NSDate date] toQueue:self.queue withHandler:^(NSArray *activities, NSError *error) {
// BREAK POINT 2
// do some processing;
block();
}];
}
The block simply creates a timer on the main queue, which periodically recalls this function, but with no completion (since I don't want to keep creating more timers).
block = ^{
dispatch_async(dispatch_get_main_queue(), ^{
self.timer = [NSTimer scheduledTimerWithTimeInterval:timerInterval
target:self selector:#selector(requestDataWithoutCompletion) userInfo:nil repeats:YES];
});
}
- (void) requestDataWithoutCompletion {
[self requestDataWithCompletion^{;}];
}
The amazing thing is that despite this set up, my app is creating timer after timer! I can't understand why.
I have placed break points in requestDataWithCompletion method. One is outside the block submitted to NSOperationQueue to get activity data (BREAKPOINT 1) and one is inside the block submitted to NSOperationQueue. (BREAKPOINT 2). Basically it shows that each time the method is called by the timer, BREAKPOINT 1 has an empty completion block (as it should be) but then strangely BREAKPOINT 2 has the completion block I submitted when I first called the function when initialising the class. Therefore, it continues to create a new timer each time the function is called by the timer. Over time, this means a massive number of timers and then the app crashes!
I have a feeling this is something to do with NSOperationQueue, but I really don't know what it could be.
In your initialisation (or when you first want to get the data and then continue getting it):
self.timer = [NSTimer scheduledTimerWithTimeInterval:timerInterval target:self selector:#selector(requestDataWithoutCompletion) userInfo:nil repeats:YES];
[self.timer fire]; //get the activity data immediately.
- (void) requestDataWithoutCompletion {
[self requestDataWithCompletion:^{}];
}
With your original requestDataWithCompletion: method. (though you could get rid of requestDataWithCompletion: and put it's code directly in requestDataWithoutCompletion if you're not using it elsewhere)
Here is my need:
I'm making an ios app that controls a device. It has an API that lets me do things like:
turnOnLights()
turnOffLights()
rotate(degrees)
move(speed)
etc... (the api is completely objective c, im just giving an example in c synthax)
From this API, I need to build high level sequences, for example:
turn on all lights
wait 1 second
turn off all lights
wait 1 second
Or
move
rotate 30 degrees
wait 1 second
move
rotate -30 degrees
I can think of hacky ways to do these with timers, but I am wondering if ObjectiveC has a nice way that I could build some high level methods so I could for example:
ReturnValue flashLights()
ReturnValue moveAndRotate()
The idea behind this would be that, the commands needed to do the flashing action would be sent repeatedly forever, and, I can do:
stopAction(returnValue)
To stop it. (I know I'm writing in C synthax but I find it clearer to explain things).
So essentially, is there a convenient way to make a script-like thing where I can call a method that starts an action. The action makes method calls, waits some time, does more method calls, and repeats this forever until the action is stopped.
Thanks
I am not sure if I understand your question properly, but if you want to repeatedly call a set of methods with delays in between, you can use aperformSelector:withObject:afterDelay, or dispatch_after to build a loop. (And there are many ways to leave the loop)
[self performSelector:#selector(resetIsBad) withObject:nil afterDelay:0.1];
or
int delayInSecond = 10;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delayInSecond * NSEC_PER_SEC),
dispatch_get_main_queue(), ^{
//doing something
});
performSelector:withObject:afterDelay invokes a method of the receiver on the current thread using the default mode after a delay.
This method sets up a timer to perform the aSelector message on the
current thread’s run loop. The timer is configured to run in the
default mode (NSDefaultRunLoopMode). When the timer fires, the thread
attempts to dequeue the message from the run loop and perform the
selector. It succeeds if the run loop is running and in the default
mode; otherwise, the timer waits until the run loop is in the default
mode.
dispatch_after add your block to a queue and if the queue is empty, it runs immediately once being added to the queue. Else it will have to wait for other tasks in the queue to finish before it can run.
More on dispatch_after:
dispatch_after
Enqueue a block for execution at the specified time.
void dispatch_after( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
Parameters:
when The temporal
milestone returned by dispatch_time or dispatch_walltime.
queue The
queue on which to submit the block. The queue is retained by the
system until the block has run to completion. This parameter cannot be
NULL.
block The block to submit. This function performs a Block_copy
and Block_release on behalf of the caller. This parameter cannot be
NULL.
Discussion
This function waits until the specified time and then
asynchronously adds block to the specified queue.
Passing DISPATCH_TIME_NOW as the when parameter is supported, but is
not as optimal as calling dispatch_async instead. Passing
DISPATCH_TIME_FOREVER is undefined.
Declared In dispatch/queue.h
Personally I don't think using an NSTimer would be 'hacky' as long as you implement it properly. You do need to make sure you invalidate the timer once you're finished with it though; check out this thread for more information about NSTimer best practices.
// in YourViewController.h
#property (nonatomic) BOOL flag;
#property (nonatomic, strong) NSTimer* timer;
// in YourViewController.m
-(void)viewDidLoad
{
[super viewDidLoad];
self.flag = YES;
[self flashLights];
// other code here
}
-(void)flashLights
{
CGFloat interval = 1.0f; // measured in seconds
self.timer = [NSTimer scheduledTimerWithTimeInterval:interval
target:self
selector:#selector(timerEventHandler)
userInfo:nil
repeats:YES];
}
-(void)timerEventHandler
{
// your API calls might take a while to execute, so consider running them asynchronously:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
if (self.flag) turnOnLights();
else turnOffLights();
self.flag = !self.flag;
});
}
-(void)stopAction
{
[self.timer invalidate];
}
Everytime I am finished with my NSTimer, I want to invalidate it and create a new interval but it keeps the old interval as well as new interval. I want to invalidate NSTimes once I click the offButton. The timer stops printing "Working" but when I call my method with a different interval, it prints "Working" for both intervals.
My code is something like this:
-(void) fireTimer{
NSString *textValue = [sliderLabel text];
float value = [textValue floatValue];
[NSTimer scheduledTimerWithTimeInterval:value target:self selector:#selector(vibrate:) userInfo:nil repeats:YES];
}
- (void) vibrate:(NSTimer*)timer {
if(_offButton.selected){
[timer invalidate];
timer=nil;
}
NSLog(#"Working");
}
You aren't following the MVC design pattern by getting your values directly from the UITextField. Those values should be stored in a model object, with the MVC pattern being used to get any new values from the text field into the model. Your current implementation is very delicate and will break in the slightest breeze. It also requires this code to have access to the UI elements, which is very inflexible; it will be better to give it access to just the model object.
Store the NSTimer * as an instance variable, and note that if you are using ARC then the NSTimer retains the target (!!) so make this instance variable __weak to break the retain-cycle.
If you want your timer to repeat then there is no need to reset it at all; this only needs to be done if the user changes the time (see point 1!).
Move the if (button_selected) do_vibrate; code into the timer fired method.
The invalidation code that you use itself is correct. But it would be easier for you to keep a reference to your timer as an ivar or property.
In that case you would definetly avoid making multiple instances of a timer.
When I execute this code:
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(showButtons) userInfo:nil repeats:NO];
do I need to nil it or release it, ot whatever for memory management?
I am using ARC
Yes, NSTimer will maintain a strong reference to the target, which can cause (especially in repeating timers) strong reference cycles (a.k.a. retain cycles). In your example, though, the timer does not repeat, and is delayed only 0.5, so worst case scenario, you will have a strong reference cycle that will automatically resolve itself in 0.5 seconds.
But a common example of an unresolved strong reference cycle would be to have a UIViewController with a NSTimer property that repeats, but because the NSTimer has a strong reference to the UIViewController, the controller will end up being retained.
So, if you're keeping the NSTimer as an instance variable, then, yes, you should invalidate it, to resolve the strong reference cycle. If you're just calling the scheduledTimerWithTimeInterval, but not saving it to an instance variable (as one might infer from your example), then your strong reference cycle will be resolved when the NSTimer is complete.
And, by the way, if you're dealing with repeating NSTimers, don't try to invalidate them in dealloc of the owner of the NSTimer because the dealloc obviously will not be called until the strong reference cycle is resolved. In the case of a UIViewController, for example, you might do it in viewDidDisappear.
By the way, the Advanced Memory Management Programming Guide explains what strong reference cycles are. Clearly, this is in a section where they're describing the proper use of weak references, which isn't applicable here (because you have no control over the fact that NSTimer uses strong references to the target), but it does explain the concepts of strong reference cycles nicely.
If you don't want your NSTimer to keep a strong reference to self, in macOS 10.12 and iOS 10, or later, you can use the block rendition and then use the weakSelf pattern:
typeof(self) __weak weakSelf = self;
[NSTimer scheduledTimerWithTimeInterval:0.5 repeats:false block:^(NSTimer * _Nonnull timer) {
[weakSelf showButtons];
}];
By the way, I notice that you're calling showButtons. If you're trying to just show some controls on your view, you could eliminate the use of the NSTimer altogether and do something like:
self.button1.alpha = 0.0;
self.button2.alpha = 0.0;
[UIView animateWithDuration:0.25
delay:0.5
options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction
animations:^{
self.button1.alpha = 1.0;
self.button2.alpha = 1.0;
}
completion:nil];
This doesn't suffer the retain issues of NSTimer objects, and performs both the delay as well as the graceful showing of the button(s) all in one statement. If you're doing additional processing in your showButtons method, you can put that in the completion block.
If you are saving it in a property, then yes, you do need to set it to nil after it fired the selector.
It's also safe to save it in case your class gets deallocated for whatever reason, so that you can [timer invalidate] if you need to.
Yes, you can use: myTimer=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(showButtons) userInfo:nil repeats:NO];
And then in your viewDidDisappear [myTimer invalidate]
Use the new method of Timer that uses closures
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: {[weak self] (timer) in
print("Tick tock")
guard let ws = self else { return }
//some action that repeats
ws.myViewControllerMethod()
})