Good way to implement this? - ios

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];
}

Related

how to add delay for a number of method calls and to start second method call only when first one finishes?

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.

Random NSTimer Inverval

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.

Bizarre behaviour with NSTimer

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)

autorefreshing uiviewcontroller in ios 5.1

I am having a UIViewController that shows progress status of an calculations that are done on my Iphone app, to see the percent of the progress I need to push button called refresh on the same UIViewController, how can I make that automatically done without the need to push the button manually
here is my code:
- (void)viewDidLoad{
[NSThread detachNewThreadSelector:#selector(autoRefresh) toTarget:self withObject:nil];
...
}
and then :
- (void) autoRefresh{
while (1) {
sleep(2);
sendFromButton = false;
if (flag == 1) { // which button pushed last
[self CaptureButton:self];
}
if (flag == 2) { // which button pushed last
[self ExampleButtonfun:self];
}
if (flag == 3) { // which button pushed last
[self CommuintyButton:self];
}
}
}
when the controller is viewed for the first time the viewDidLoad is called that creates a thread to run the autorefresh function , but that controller is not refreshed although I did it in the right way I guess!, please help me with that.
If you want to do a series of calculations, and show that progress, you have to do the calculations in a background thread, and then update the UI on the main thread. If you did your calculations in the main thread, your UI would never have a chance to update itself.
But, assuming that you've successfully initiated your time consuming calculations on a background thread, you could then use a timer (or display link) to update your progress bar, for example, define a timer property:
#property (nonatomic, weak) NSTimer *timer;
Then schedule a repeating timer from the main queue and start your background process:
// start the timer
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0
target:self
selector:#selector(updateProgress:)
userInfo:nil
repeats:YES];
self.timer = timer;
// use whatever mechanism you want for initiating your background process (though dispatch queues and operation queues may be easier than dealing with threads directly)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self startTimeConsumingProcess]
});
You'd then obviously implement an updateProgress: method that updates your UI:
- (void)updateProgress:(NSTimer *)timer
{
// update the UI progress bar here
}
And don't forget to invalidate that timer when your calculation is done or when the view is dismissed, because if you don't, the timer will maintain strong reference to your view controller and you'll have a strong reference cycle (aka retain cycle).
By the way, the other logical approach, instead of using a timer, is to just have the background calculation dispatch UI updates that update the progress bar back to the main queue, e.g.
- (void) startSomeTimeConsumingProcess
{
// start time consuming process in background queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL finished = NO;
while (!finished)
{
// do something, updating `finished` when done
// update UI to report the progress in the main queue, though:
dispatch_async(dispatch_get_main_queue(), ^{
// update progress UI here
});
}
});
}
The only consideration with this approach is how quickly this background process dispatches the main queue with progress bar updates. If it updates too quickly, you can backlog the main queue with progress updates. Hence, the appeal of the timer (or display link) based approach, above. But if you're confident that these updates will happen slowly enough, this alternative approach might be easier.

NSOperationQueue's threads just don't die

Sorry, it's a bit wordy, but I wanted to make sure I was clear! ;-)
I have an iOS app that uses FFMPEG for streaming RTSP. I've multi-threaded FFMPEG using NSOperationQueue such that most its work, other than painting the image to the screen, of course, happens in background threads.
Works great! ...except for the fact that threads the NSOperationQueue creates never die!
I init the Queue in the class' init method with:
self->opQ = [[NSOperationQueue alloc] init];
[self->opQ setMaxConcurrentOperationCount:1];
I add methods to the Queue using blocks:
[self->opQ addOperationWithBlock:^{
[self haveConnectedSuccessfullyOperation];
}];
Or
[self->opQ addOperationWithBlock:^{
if (SOME_CONDITION) {
[self performSelectorOnMainThread:#selector(DO_SOME_CRAP) withObject:nil waitUntilDone:NO];
}
}];
Later, when I need to tear down the RTSP stream, in addition to telling FFMPEG to shut down, I call:
[self->opQ cancelAllOperations];
Which does indeed stop the threads from doing any work , but never actually destroys them. Below, you'll see a screen shot of threads that are doing nothing at all. This is what my threads look like after starting/stoping FFMPEG several times.
I seem to remember reading in Apple's documentation that NSOperations and the threads they are run on are destroyed once they are done executing, unless otherwise referenced. This doesn't appear to be the case.
Do I just need to destroy the NSOperationQueue, then re-init it when I need to start up FFMPEG again (I just realized I haven't tried this)? Anyone know how I need to kill these extra threads?
THANKS!
I solved it by creating NSBlockOperations so that I could monitor the isCancelled state, while also making the new NSBlockOperations' content more intelligent, such that I simplified the routine that would add the operations to the queue.
... Plus, I made an NSOperationQueue n00b mistake: I was adding operations to the queue on a looping basis, which fired up to 30 times per second (matching the video's frame rate). Now, however, the operation is added to the queue only once and the looping behavior is contained within the operation instead of having the loop add the operation to the queue.
Previously, I had something like this (pseudo code, since I don't have the project with me):
NSTimer *frameRateTimeout = [NSTimer scheduledTimerWithTimeInterval:1/DESIRED_FRAMES_PER_SECOND target:self selector:#selector(ADD_OPERATION_TO_QUEUE_METHOD:) userInfo:nil repeats:YES];
-(void)ADD_OPERATION_TO_QUEUE_METHOD:(NSTimer *)timer {
[opQ addOperation:displayFrame];
}
Which worked well, as the OS would correctly manage the queue, but it was not very efficient, and kept those threads alive forever.
Now, it's more like:
-(id)init {
self = [super init];
if (self) {
// alloc/init operation queue
...
// alloc/init 'displayFrame'
displayFrame = [NSBlockOperation blockOperationWithBlock:^{
while (SOME_CONDITION && ![displayFrame isCancelled]) {
if (playVideo) {
// DO STUFF
[NSThread sleepForTimeInterval:FRAME_RATE];
}
else { // teardown stream
// DO STUFF
break;
}
}
}];
}
return self;
}
- (void)Some_method_called_after_getting_video_ready_to_play {
[opQ addOperation:displayFrame];
}
Thanks, Jacob Relkin, for responding to my post.
If anyone needs further clarification, let me know, and I'll post better code once I have the project in my hands again.

Resources