I'm very very new to iOS programming but I already have a big issue I can't solve. It seems so easy.
I have a button, I click on it to change a label called message
Here is the code:
- (IBAction)react:(id)sender {
int hasard ;
hasard=3;
message.text=[NSString stringWithFormat:#"1 %d",hasard];
sleep (1);
message.text=[NSString stringWithFormat:#"2 %d",hasard];
}
It works well but I don't see the first message.text change.
When I click the button, I have to wait one second and I see 2 3
I thought I could see 1 3, wait a second and then see 2 3.
What is missing? It seems so obvious.
sleep() will suspend the current threads execution which is the main thread so you are blocking all UI operations until the method completes. You need to schedule the second assignment to run after the specified time without blocking the current run loop. This can be achieved with GCD.
- (IBAction)react:(id)sender {
int hasard ;
hasard=3;
message.text=[NSString stringWithFormat:#"1 %d",hasard];
int64_t delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
message.text=[NSString stringWithFormat:#"2 %d",hasard];
});
}
You are blocking the main thread (well, rather sleep() blocks it). If you do so, committed changes to the UI won't appear - you'll only see the final result. You have to do something else (blocking the UI is a very bad idea in terms of user experience, by the way). You can try using a timer, for example:
int hasard = 3;
- (void)react:(id)sender
{
message.text = [NSString stringWithFormat:#"1 %d", hasard];
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(timer:) userInfo:nil repeats:NO];
}
- (void)timer:(NSTimer *)tmr
{
message.text = [NSString stringWithFormat:#"2 %d", hasard];
}
Related
I have created a drum sequencer using Objective C. I want the slider to control the tempo. At the moment, everything works, and the interval between each step is being controlled by:
while (self.running)
{
// sleep until the next step is due
[NSThread sleepUntilDate:time];
// update step
int step = self.step + 1;
// wrap around if we reached NUMSTEPS
if (step >= NUMSTEPS)
step = 0;
// store
self.step = step;
// time duration until next step
time = [time dateByAddingTimeInterval:0.2];
}
So the time between each step is 0.2 seconds. I have tried to implement a tempo slider like so in the view controller .m (the slider has a range of 0.3 to 1.0 so will output similar value to what is currently in time):
- (IBAction)sliderMoved:(UISlider *)sender
{
AppDelegate* app = [[UIApplication sharedApplication] delegate];
app.tempo = sender.value;
}
and by changing the line in the while(self.running) thread to:
time = [time dateByAddingTimeInterval: (NSTimeInterval) _tempo];
However, this causes the time between steps to be far too short (tempo is crazy fast) and when any control in the app is touched, it crashes.
I wonder if I need to set up a function like this, but I'm not sure what would go inside to enable the tempo slider to work:
- (void)setTempo:(float)tempo
{
}
I have tried to be as clear as I can, if anyone can help me I'd be very grateful, thanks in advance
-(void) startDrumTick{
[self.myDrumTimer invalidate]; // stop any current existing timer
// perform the call to the method 'drumMethodOperation:'
// every 0.2 sec. NB: drumMethodOperation will run on main thread.
// this means that if you expect to do long-blocking operation,
// you will need to move that op to an async thread, in order to avoid
// the UI blocking
self.myDrumTimer = [NSTimer scheduledTimerWithTimeInterval:0.2
target:self
selector:#selector(drumMethodOperation:)
userInfo:nil
repeats:YES];
}
-(void)drumMethodOperation:(id)sender
{
// update step
int step = self.step + 1;
// wrap around if we reached NUMSTEPS
if (step >= NUMSTEPS)
step = 0;
// store
self.step = step;
// any other needed operation to run every 0.2 secs
}
Below an example for an async thread management using GCD
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
// Long blocking operation ( DO NOT PERFORM ANY UI OPERATION, like changing a text label, setting an image to an UIImageView, etc. )
[self myLongDbQuery];
dispatch_async(dispatch_get_main_queue(), ^(void){
//Perform you UI Updates here
self.myLabel.text = #"Query done!!!";
});
});
Hope it helps
Luca is right about using GCD. If talk about your initial solution.
Did you set initial value for _tempo? Looks like your bug can be caused by _tempo = 0 initially. As you understand sliderMoved will be called only after some user action so you need to set initial value.
I have the method in which I need to make a 5 sec delay every time I call it. Firstly I make it with sleep(5); - it worked excellent but I believe - it's not obj-c way, so I tried to write it with GCD help. The first call of this procedure make a delay about 5 sec, but other calls in this queue are going one after one without delay. How to solve this problem?
- (void) buyItemAtUUID:(NSString*)UUID
{
dispatch_barrier_async(dataManagerQueue, ^{
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dataManagerQueue, ^(void){
NSInteger path = [self indexFromObjectUUID:UUID];
if (path != NSNotFound)
{
NSMutableDictionary *item = [[_items objectAtIndex:path] mutableCopy];
NSNumber *value = [NSNumber numberWithFloat:[[item objectForKey:#"Quantity"] floatValue] - 1.0];
}
});
});
}
The first call of this procedure make a delay about 5 sec, but other
calls in this queue are going one after one without delay.
That's usually desired behavior. The reason you shouldn't call sleep() is that it'll block the thread, preventing anything else in that thread from happening. If you block the main thread, your device will appear to freeze, which isn't a very nice user experience.
GCD provides a nice dispatch_group_wait() function that lets you make one set of tasks wait for some other group to finish. Take a look at Waiting on Groups of Queued Tasks for an example.
dispatch_barrier_async only stops blocks added to the concurrent queue after this block from running. The blocks that were already queued are not prevented from running.
I'm making a while loop and I'm trying to delay it.
while (mode == 1)
{
[self performSelector:#selector(on) withObject:nil afterDelay:slider.value];
NSLog(#"on");
[self performSelector:#selector(off) withObject:nil afterDelay:slider.value];
NSLog(#"off");
}
But even though I'm setting the slider to 10 seconds it goes on and off very fast.
Also my app black screens and I only see the status bar and my nslog but that might have to do with something else.
performSelector:withObject:afterDelay: does not wait for the selector to finish. This means that as soon as you are telling selector A to run after a certain time, it immediately goes on and tells selector B to run after a certain time. There are plenty of options to fix this:
If you would like to stick with selectors, you could use performSelector:onThread:withObject:waitUntilDone:. Make sure you don't use the main thread though or you will experience UI freezes.
[NSThread waitForInterval] is another option but, like the previous option, will freeze the UI unless you are calling the entire while loop on a different thread. I am surprised this has been mentioned so much without people noting this important factor.
GCD is another option. It doesn't wait for it to finish so you shouldn't experience major UI freezes.
dispatch_time_t dispatchTime = dispatch_time(DISPATCH_TIME_NOW, slider.value * NSEC_PER_SEC);
dispatch_after(dispatchTime, dispatch_get_main_queue(), ^(void){
[self on];
dispatch_after(dispatchTime, dispatch_get_main_queue(), ^(void){
[self off];
})
});
Another option is to keep what you are doing now and make selector B run after slider.value*2. If you think of this in a mathematically way, it makes sense:
A) 0-1-2-3-4-5-6-7-8-9-10
B) 0-1-2-3-4-5-6-7-8-9-10-1-2-3-4-5-6-7-8-9-10
This is what you're telling your app:
"Turn on something in slider.value seconds from now"
"Turn off something in slider.value seconds from now"
Do you see a problem with that?
double delayInSeconds = slider.value;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self off];
//or
[self on];
});
This performs whatever inside the block after given time! :)
The while loop doesn't wait for the delayed actions to complete, it simply schedules them. The problem is that you're scheduling all the events in a very quick loop that then runs the events one after the other just as quickly, all 10 seconds later.
[self performSelector:#selector(on) withObject:nil afterDelay:slider.value];
In this statement afterDelay doesn't mean that it will pause for some second.
It means that method "on" will execute after some second. But execution will not wait to finish execution of method"on" and go to next statement.
If you want to make only some delay than you can use following statement :
[NSThread sleepForTimeInterval:2.0];
This will wait for 2 second. But It may not be good idea because it will freeze your UI.
I hope this will help you.
All the best.....
If you want to turn it on immediately and turn it off the specified delay:
[self on];
NSLog(#"on");
[self performSelector:#selector(off) withObject:nil afterDelay:slider.value];
NSLog(#"off");
If you want to turn it on after the specified delay, then turn it off again 10 seconds after that:
[self performSelector:#selector(on) withObject:nil afterDelay:slider.value];
NSLog(#"on");
[self performSelector:#selector(off) withObject:nil afterDelay:slider.value + 10];
NSLog(#"off");
try to use [NSThread sleepForTimeInterval:1.0f]; it will stop your thread based on the time u are providing
I'm creating a program that can, amoung other things, fade in and out music. The problem is that other threads/queues can pause the music, meaning that the fade in and out also need to not only pause, but hold off. I need to be able to pause the "timer" on a dispatch_after (because this is called when the music starts playing in order to tell it when to start fading out, which would need to be delayed if it was paused) and pause a queue itself (in order to pause a fade in or fade out WHILE they're fading in or out)
Here's the code (fadeIn and delayFadeOut are both called at the start of the program):
- (void) doFadeIn: (float) incriment to: (int) volume with: (AVAudioPlayer*) thisplayer on: (dispatch_queue_t) queue{
dispatch_async(queue, ^{
double delayInSeconds = .1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, queue, ^(void){
thisplayer.volume = (thisplayer.volume + incriment) < volume ? thisplayer.volume + incriment : volume;
NSLog([[[NSNumber alloc] initWithFloat:thisplayer.volume] stringValue]);
if (thisplayer.volume < volume) {
[self doFadeIn:incriment to:volume with:thisplayer on:queue];
}
});
});
}
-(void) doDelayFadeOut: (float) incriment with: (AVAudioPlayer*) thisplayer on: (dispatch_queue_t) queue
{
dispatch_async(queue, ^{
double delayInSeconds = .1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, queue, ^(void){
thisplayer.volume = (thisplayer.volume - incriment) > 0 ? thisplayer.volume - incriment : 0;
NSLog([[[NSNumber alloc] initWithFloat:thisplayer.volume] stringValue]);
if (thisplayer.volume > 0.0) {
[self doDelayFadeOut:incriment with:thisplayer on:queue];
}
});
});
}
-(void) fadeIn: (AVAudioPlayer*) dFade {
if (dFade == nil) {
return;
}
dispatch_queue_t queue = dispatch_queue_create("com.cue.MainFade", NULL);
dispatch_async( queue, ^(void){
if(dFade !=nil){
double incriment = ([self relativeVolume] / [self fadeIn]) / 10; //incriment per .1 seconds.
[self doFadeIn: incriment to: [self relativeVolume] with:dFade on:dispatch_queue_create("com.cue.MainFade", 0)];
}
});
}
- (void) delayFadeOut: (AVAudioPlayer*) dFade { //d-fade should be independent of other threads
if (dFade == nil) {
return;
}
int timeRun = self.duration - self.fadeOut;
dispatch_queue_t queue = dispatch_queue_create("com.cue.MainFade", NULL);
dispatch_time_t mainPopTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeRun * NSEC_PER_SEC));
printf("test");
dispatch_after(mainPopTime, queue, ^(void){
if(dFade !=nil){
double incriment = ([dFade volume] / [self fadeOut])/10; //incriment per .1 seconds.
[self doDelayFadeOut:incriment with:dFade on:dispatch_queue_create("com.cue.MainFade", 0)];
}
});
if (self.cueType == 2) {
[self callNext];
}
}
To your general question, the call is dispatch_suspend() (along with dispatch_resume()). This will prevent any new blocks on a particular queue from being scheduled. It will not have any impact on already-running blocks. If you want to pause a block already scheduled and running, it is up to your code to check some conditional and pause.
The key to understand when using this, though, is that there is nothing that can "pause the "timer" on a dispatch_after." If you say you want something to be dispatched after 1 second, it absolutely will be dispatched after 1 second. But "dispatch" does not mean "run." It means "put on a queue." If that queue is suspended, then the block will hang out until the queue is resumed. The thing to be careful about is that you don't want a bunch of fade blocks to accumulate on the queue. If they did, when you resume the queue they would all be scheduled back-to-back. Looking at your code, that probably won't happen, so this could work for you. Just keep in your mind that dispatch means "put on a queue." And non-suspended queues have their blocks scheduled in order.
For suspending queues/cancelling operations, you should use NSOperation and NSOperationQueue instead of GCD. See here.
I have a need to delay for a certain amount of time and yet allow other things on the same runloop to keep running. I have been using the following code to do this:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
This seems to do exactly what I want, except that sometimes the function returns immediately without waiting the desired time (1 second).
Can anyone let me know what could cause this? And what is the proper way to wait while allowing the run loop to run?
NOTE: I want to delay in a manner similar to sleep(), such that after the delay I am back in the same execution stream as before.
You should use GCD and dispatch_after for that. It is much more recent and efficient (and thread-safe and all), and very easy to use.
There is even a code snippet embedded in Xcode, so that if you start typing dispatch_after it will suggest the snippet and if you validate it will write the prepared 2-3 lines for you in your code :)
int64_t delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
<#code to be executed on the main queue after delay#>
});
Use an NSTimer to fire off a call to some method after a certain delay.
Have you tried performSelector:withObject:afterDelay:?
From the Apple documentation
Invokes a method of the receiver on the current thread using the default mode after a delay.
I had a similar issue and this is my solution. Hope it works for others as well.
__block bool dispatched = false;
while ( put your loop condition here )
{
if (dispatched)
{
// We want to relinquish control if we are already dispatched on this iteration.
[ [ NSRunLoop currentRunLoop ] runMode: NSDefaultRunLoopMode beforeDate:[ NSDate date ] ];
continue;
}
// mark that a dispatch is being scheduled
dispatched = true;
int64_t delayInNanoSeconds = (int64_t) (0.1 * (float) NSEC_PER_SEC);
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, delayInNanoSeconds);
dispatch_after(delayTime, dispatch_get_main_queue(), ^() {
// Do your loop stuff here
// and now ready for the next dispatch
dispatched = false;
} );
} // end of while