I'm looking to create a countdown timer for SMPTE Timecode (HH:MM:SS:FF) on iOS. Basically, it's just a countdown timer with a resolution of 33.33333ms. I'm not so sure NSTimer is accurate enough to be counted on to fire events to create this timer. I would like to fire an event or call a piece of code every time this timer increments/decrements.
I'm new to Objective-C so I'm looking for wisdom from the community. Someone has suggested the CADisplayLink class, looking for some expert advice.
Try CADisplayLink. It fires at the refresh rate (60 fps).
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(timerFired:)];
displayLink.frameInterval = 2;
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
This will fire every 2 frames, which is 30 times per seconds, which seems to be what you are after.
Note, that this is tied to video frame processing, so you need to do your work in the callback very quickly.
You basically have no guarantees with either NSTimer or dispatch_after; they schedule code to triggered on the main thread, but if something else takes a long time to execute and blocks the main thread, your timer won't fire.
That said, you can easily avoid blocking the main thread (use only asynchronous I/O) and things should be pretty good.
You don't say exactly what you need to do in the timer code, but if all you need to do is display a countdown, you should be fine as long as you compute the SMPTE time based on the system time, and not the number of seconds you think should have elapsed based on your timer interval. If you do that, you will almost certainly drift and get out of sync with the actual time. Instead, note your start time and then do all the math based on that:
// Setup
timerStartDate = [[NSDate alloc] init];
[NSTimer scheduledTimer...
- (void)timerDidFire:(NSTimer *)timer
{
NSTImeInterval elapsed = [timerStartDate timeIntervalSinceNow];
NSString *smtpeCode = [self formatSMTPEFromMilliseconds:elapsed];
self.label.text = smtpeCode;
}
Now you will display the correct time code no matter how often the timer is fired. (If the timer doesn't fire often enough, the timer won't update, but when it updates it will be accurate. It will never get out of sync.)
If you use CADisplayLink, your method will be called as fast as the display updates. In other words, as fast as it would be useful, but no faster. If you're displaying the time, that's probably the way to go.
If you are targeting iOS 4+, you can use Grand Central Dispatch:
// Set the time, '33333333' nanoseconds in the future (33.333333ms)
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 33333333);
// Schedule our code to run
dispatch_after(time, dispatch_get_main_queue(), ^{
// your code to run here...
});
This will call that code after 33.333333ms. If is this going to be a loop sorta deal, you may want to use the dispatch_after_f function instead that uses a function pointer instead of a block:
void DoWork(void *context);
void ScheduleWork() {
// Set the time, '33333333' nanoseconds in the future (33.333333ms)
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 33333333);
// Schedule our 'DoWork' function to run
// Here I pass in NULL for the 'context', whatever you set that to will
// get passed to the DoWork function
dispatch_after_f(time, dispatch_get_main_queue(), NULL, &DoWork);
}
void DoWork(void *context) {
// ...
// Do your work here, updating an on screen counter or something
// ...
// Schedule our DoWork function again, maybe add an if statement
// so it eventually stops
ScheduleWork();
}
And then just call ScheduleWork(); when you want to start the timer. For a repeating loop, I personally think this is a little cleaner than the block method above, but for a one time task I definitely prefer the block method.
See the Grand Central Dispatch docs for more info.
Related
I must be misunderstanding dispatch_group because my dispatch_group_notify call is running before the end of the async calls made within individual dispatch_group_async blocks. Here's my code:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t dispatchGroup = dispatch_group_create();
// create operation for each HKTypeIdentifier for which we want to retrieve information
for( NSString *hkType in typesToRetrieve){
dispatch_group_async(dispatchGroup, queue, ^{
// this method runs several HK queries each with a completion block as indicated below
[self getDataForHKQuantity: hkType withCompletion:^(NSArray *results) {
// this completion blocks runs asynchronously as HK query completion block
// I want to runCompletionBlock only after
// all these processResultsArray calls have finished
[self processResultsArray:results];
}];
});
}
dispatch_group_notify(dispatchGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self runCompletionCheck];
});
The method getDataForHKQuantity in turn runs an asynchronous query to HealthKit with a completion block. I need to run runCompletionCheck after all these completion blocks for the HealthKit queries have run, but what is happening now is that runCompletionCheck is running before the code in the queries' completion blocks has run. To me that means that dispatch_group_notify along with dispatch_group_async don't work the way I need, so what am I doing wrong or what's the best way to handle this?
Overall goal: make a bunch of concurrent queries to HealthKit, run their completion blocks, then when all those completion blocks run, run a final method.
The problem is two fold. First, the health kit queries don't always run their completion blocks. I started by using a counter system, with a counter in the health kit queries' completion blocks. That's what told me that these completion blocks don't always run. Second, I don't know how many queries I am trying to run because it depends on what data sources the user has.
So, question, how can I wait until all the completion blocks from a series of health kit queries have run before running a final method?
Your -getDataForHKQuantity:withCompletion: method is asynchronous. So, through your dispatch groups you are syncing the calls to these methods, but not the work done in the methods themselves.
In other words, you are nesting two asynchronous calls, but syncing only the first level through you dispatch groups.
You'll need to come up with a different strategy for controlling your program flow.
Two examples:
1. Using Semaphores (blocking)
Some time ago, I used semaphores for a similar task, not sure it's the best strategy, but in your case it would go sth like:
semaphore = dispatch_semaphore_create(0);
for( NSString *hkType in typesToRetrieve)
{
[self getDataForHKQuantity: hkType withCompletion:^(NSArray *results) {
// register running method here
[self processResultsArray:results];
if (isLastMethod) // need to keep track of concurrent methods running
{
dispatch_semaphore_signal(semaphore);
}
}];
}
// your program will wait here until all calls to getDataForHKQuantity complete
// so you could run the whole thing in a background thread and wait for it to finish
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
2. Using dispatch_group
dispatch_group_t serviceGroup = dispatch_group_create();
for( NSString *hkType in typesToRetrieve)
{
dispatch_group_enter(serviceGroup);
[self getDataForHKQuantity: hkType withCompletion:^(NSArray *results) {
[self processResultsArray:results];
dispatch_group_leave(serviceGroup);
}];
}
dispatch_group_notify(serviceGroup,dispatch_get_main_queue(),^{
// Won't get here until everything has finished
});
Also check this link for further info.
I have a bare single-view iOS app with the following in the -viewDidLoad of the view:
dispatch_queue_t q_default;
q_default = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, q_default); //run event handler on the default global queue
dispatch_time_t now = dispatch_walltime(DISPATCH_TIME_NOW, 0);
dispatch_source_set_timer(timer, now, 30ull*NSEC_PER_SEC, 5000ull);
dispatch_source_set_event_handler(timer, ^{
printf("test\n");
});
dispatch_resume(timer);
This is taken directly from the docs (except for a simplified printf() argument). The block is never executed--can someone tell me why??
Additional Information
I was trying this in a larger app to no avail. I then backed out to the barebones app, tried with ARC both on and off, tried this code in -appDidFinishLaunching..., all with no luck. I can surround this code with NSLogs, both of which are printed. I've checked timer--it is not nil.
So, the problem was that I'd lost my reference to timer when the surrounding scope was destroyed. Changing timer to an ivar instead of an automatic variable fixed things...
Per the documentation of dispatch_source_create:
Dispatch sources are created in a suspended state. After creating the
source and setting any desired attributes (for example, the handler or
the context), your application must call dispatch_resume to begin
event delivery.
So your timer never fires because it's suspended. To end its suspension you need to call dispatch_resume.
I want to know what's equivalent to using AVAudioPlayerDelegate's audioPlayerDidFinishPlaying:successfully: method in OpenAL. For example:
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
// (code or conditions for when the audio has finished playing...)
}
Generally speaking, OpenAL won't notify you when audio has finished playing, so there's no real equivalent to the AVAudioPlayerDelegate. The easiest way is to simply delay a function/block call by the length of the audio. As an example, you could use libdispatch (aka Grand Central Dispatch) to add a block to a queue after a set amount of time:
dispatch_time_t delay;
dispatch_queue_t queue;
dispatch_block_t block;
uint64_t audio_length_ns = 10000000000; // 10 seconds
delay = dispatch_time(DISPATCH_TIME_NOW, audio_length_ns);
queue = dispatch_get_main_queue();
block = ^{
// Do whatever you need to after the delay
// Maybe check to see if the audio has actually
// finished playing and queue up the block again
// if it hasn't.
};
// Queue up the block for the time after
dispatch_after(delay, queue, block);
The slightly harder way is, as mentioned in the comment inside the block, to check if OpenAL is finished in the block and, if it isn't, to push the block onto the queue again (probably with a shorter delay, especially if you can approximate how much longer it'll be). In general, though, you probably won't need to be spot-on and just being in a decent range of the sound's completion is good enough.
You can also schedule this sort of thing via other methods, like performSelector:withObject:afterDelay:, but that comes down more to your preference as far as API is concerned. The idea is pretty much the same.
I need to do a possibly long series of calls that must occur on the main thread (because otherwise UIKit will balk). By "long" I mean 10,000 operations lasting .1 second each on an iPad 3.
Obviously, It's probably not the best idea to just loop through all of them at once.
I don't know how to execute all these on the main thread while leaving enough breathing room to keep UIKit responsive and the watchdog asleep (ie. not get terminated for hogging the run loop).
Does anybody have an idea? I will be targeting iOS 5.
Specifically what I'm trying to do is cache UITextPositions, because a UITextView is apparently taking a non-cached, iterative approach at getting UITextPositions, which means it is very, very slow at doing positionFromPosition:textview.beginningOfDocument offset:600011, but much faster at getting positionFromPosition:aPositionAt600000 offset:11. In fact, in my test case, the former takes over 100 seconds (on the main thread!), while the latter is virtually instantaneous.
Why do you want to do it on the main thread? The typical answer is to do these operations on a background thread, and send UI updates back to the main thread. For example, you could use Grand Central Dispatch:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do my time consuming task and everytime it wants to update the UI,
// it should dispatch that back to the main queue, e.g.
for (NSInteger i = 0; i < 10000; i++)
{
// do my background work
// now update the UI
dispatch_async(dispatch_get_main_queue(), ^{
// update the UI accordingly
});
}
});
Update:
It sounds like you have to do this in the foreground, so perhaps using a NSTimer might be better. I'm not a big NSTimer guy, but it might look something like the following.
First, make sure you have a class instance variable for it:
NSTimer *_timer;
Next, you can initialize it with:
- (void)startTimer
{
_timer = [NSTimer timerWithTimeInterval:0.0 target:self selector:#selector(timerCallback:) userInfo:nil repeats:YES];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addTimer:_timer forMode:NSDefaultRunLoopMode];
}
This will then invoke the timerCallback, perhaps processing a single UITextPosition on each invocation:
- (void)timerCallback:(NSTimer*)theTimer
{
BOOL moreTextPositionsToCalculate = ...;
if (moreTextPositionsToCalculate)
{
// calculate the next UITextPosition
}
else
{
[self stopTimer];
}
}
and when you're done, you could stop your timer like so:
- (void)stopTimer
{
[_timer invalidate];
_timer = nil;
}
My application does a lot of work with a bunch of For loops. It calculates a massive amount of strings, and it can take over a whole minute to finish.
So I placed a NSProgressIndicator in my app.
Within the loops, I used the "incrementBy" function of the NSProgressIndicator. However, I don't see the actual bar filling up.
I suspect that's because of the loops taking all power possible, and thus the NSProgressIndicator is not updated (graphically).
How would I make it progress then?
Are your for loops running on the main thread or in a background thread? If they're running on the main thread, the GUI will never get a chance to update itself to reflect the progress change as this will only happen at the end of the runloop, i.e. after your functions have finished running.
If your for loops are running in the background, you're being naughty! You shouldn't update the GUI from anywhere but the main thread. If you're targeting a modern system, you can use GCD to trivially work around this.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
for (int i = 0; i < n; i++) {
// do stuff
dispatch_async(dispatch_get_main_queue(), ^(void) {
// do your ui update here
});
}
});
Alternatively, you can rewrite your for loops to take advantage of GCD even further and use dispatch_apply. The equivalent of the above would be:
dispatch_apply(n, DISPATCH_QUEUE_PRIORITY_DEFAULT, ^(size_t i) {
// for loop stuff here
dispatch_async(dispatch_get_main_queue(), ^(void) {
// do your ui update here
});
});
Note that using dispatch_apply means that each "iteration" of the loop may run concurrently with respect to one another, so this won't be applicable if your for loop requires to be run in a serial fashion.