Determining when OpenAL has finished playing audio using callbacks - objective-c

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.

Related

Is it good practice to send a delayed semaphore_signal to a semaphore in a macOS Grand Central Dispatch App?

I am using semaphores and GCD in a macOS Objective-C Application. I have this common scenario :
dispatch_semaphore_t fd_sema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_main_queue(), ^{
// Perform some activity
// But can potentially never complete
dispatch_semaphore_signal(fd_sema);
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_semaphore_signal(fd_sema);
});
dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER);
Basically, here I am trying to obtain a timeout on a operation. Given that the argument of dispatch_semaphore_wait can only be now or forever, I am using the dispatch_after block to implement a timeout. Do you think this is good practice or it can lead to problems and crashes ? Thanks for any help.
Where'd you get the idea that the timeout on dispatch_semaphore_wait() can only be now or forever? That's not correct. You can pass the result from dispatch_time() just like you're using with dispatch_after(). Therefore, there's no need to implement a timeout yourself.

How to wait for completion of all asynchronous tasks before running final tasks

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.

Canceling previous call of dispatch_async

I'm implementing a tracking mechanism of window A following the position of window B. Window B sending events of its position and window B reacts to those events by calling to setWindowProperties:
void setWindowProperties(bool topMost, bool visible,
CGWindowID parentWindow, CGWindowID aboveWindow,
NSRect windowFrame, NSRect viewFrame, bool isAbove)
{
dispatch_async(dispatch_get_main_queue(), ^{
setWindowPropertiesImpl(topMost, visible, parentWindow, aboveWindow, windowFrame, viewFrame, isAbove);
});
}
But, because of too much events sent by window B I'm getting a "snake tracing" effect. I want to get over it by reacting only to the last position event, meaning, canceling all previous call to :
dispatch_async(dispatch_get_main_queue(), ^{
setWindowPropertiesImpl(topMost, visible, parentWindow, aboveWindow, windowFrame, viewFrame, isAbove);
});
And as a result, leaving in the queue only the last position event - the only one that matters.
My question: Is there a way to cancel all previous calls for of dispatch_async?
Yes, dispatch tasks are now cancelable, but when there are events that are coming in more quickly than the main queue can process them, it's sometimes useful to use a dispatch source. Specifically a DISPATCH_SOURCE_TYPE_DATA_ADD data source.
// create source (and save this reference somewhere so it doesn't get released on you)
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
// specify what you want the event handler to do
dispatch_source_set_event_handler(source, ^{
// whatever you want to do
});
// start the dispatch source
dispatch_resume(source);
Then, when you want to trigger this, rather than doing dispatch_async, you would:
dispatch_source_merge_data(source, 1);
Clearly, this means that the event handler has to pull the data from the other window rather than pushing it, but hopefully this illustrates the basic idea.
For more information see WWDC 2012 video Asynchronous Design Patterns with Blocks, GCD, and XPC. Specifically, see design pattern 8, "Update State Asynchronously" in the latter part of the video.
You cannot cancel an operation enqueued on a dispatch queue.
GCD queues have no way of cancelling a block once it scheduled. The architecture is very much "fire and forget".
Instead of GCD you could use NSOperationQueue that can also async executes, then you can cancel.

How do I create an accurate timer event in Objective-C/iOS?

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.

UI does not update when main thread is blocked in Cocoa app

I am using a NSProgressIndicator in my main thread to update on progress as I run through my entire method. Now when I end up calling an object from a different class file, and wait for that object to return to a value to my main thread, I notice that the NSProgressIndicator will disappear. I understand that this is because the main thread is blocked until I get the return value from the other object.
So my questions is what is the recommended way for updating UI in the main thread without blocking it and having other objects run in the background and return values to the main thread as needed. I know how to use blocks but blockoperations are not allowed to return values.
What I need is something that helps this pseudo code:
-(IBAction) main {
//Update progress indicator UI to show progress
//perform an call to another object from another class.
// wait till i get its return value.
//Update progress indicator UI to show progress
// Use this return value to do something.
//Update progress indicator UI to show progress
}
When the call to the other object is made, I notice that the determinate NSProgressIndicator I have completely disappears since the main thread is blocked. Thanks.
Your above code is not the correct approach. Since main never returns, the progress indicator will never update. You must return quickly on the main thread.
Instead, what you want to do is set up a background block that at various points updates the progress indicator on the main thread. So, for instance:
- (IBAction)start:(id)sender {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{[self.progress setProgress:0];});
// Doing some stuff
dispatch_async(dispatch_get_main_queue(), ^{[self.progress setProgress:.25];});
// Doing more stuff
dispatch_async(dispatch_get_main_queue(), ^{[self.progress setProgress:.75];});
});
}
(Yes, this causes the queue to retain self, but that's ok here because self is not retaining the queue.)
You can achieve what you are looking for with GCD (Grand Central Dispatch).
Here is an example to get you started:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
// Perform async operation
dispatch_sync(dispatch_get_main_queue(), ^{
// Update UI
});
});
It sounds like your operation should be run in a separate thread which can be done several ways but is probably most easily achieved using NSOperationQueue and either custom NSOperation classes (it's easier than it sounds to set these up) or use of the NSInvokeOperation class.
Then you can send messages back to your class in the main thread using the NSNotificationCenter or set up as an observer using Key-Value Observing (KVO).
Bottom line, you have a variety of choices and to make the best one should have an understanding of the underlying technologies. I'd start with Apple's Threaded Programming Guide personally, then read it a second time to be sure you extracted all the goodness before building out your solution.