Calling methods with delays/sleeps in between - ios

I am in the middle of writing a music game for iOS. What I am trying to do is have the computer play a tune, which involves calling a method that plays a sound, pausing, and calling another method that plays a different sound.
So what I am trying to figure out is how to call these methods with delays/sleeps. I have found two ways that are very cumbersome. the first is do use
[self performSelector:#selector(startNotePlay:) withObject:button afterDelay:.5];
[self performSelector:#selector(startNotePlay:) withObject:button afterDelay:2.5];
etc etc...
However this way is very annoying because all the methods are called at once so the delay has to keep into account how long the pauses are and how long the notes before are playing. so a lot of math would need to be done...
The second way is to use sleep, which is much easier.
[self performSelector:#selector(startNotePlay:) withObject:button afterDelay:.5];
sleep(2.0);
[self performSelector:#selector(startNotePlay:) withObject:button afterDelay:1.5];
the problem with this is that sleep shuts down the main thread. So I can't do any UI changes while sleep is happening.
Is there anything that is in between these two? Basically is there anyway to perform sleep without shutting down everything. Or something like, perform this method, then perform that method after X delay?
Thanks for the help,
Gabe

How about performing one selector and then perform the other one INSIDE this one after a delay? that way you know the one is gonna go off after the other one? :)
Otherwise you can maybe execute a method on a certain time using timeIntervalSinceNow

Just based on this information, I would suggest running this code on a background thread. I assume your note playing code is necessarily running on the main thread. You can, more or less, get the timing you're looking for using something like this.
dispatch_queue_t queue = dispatch_queue_create("musicQueue", NULL);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue() ^{
[self performSelector:#selector(startNotePlay:) withObject:button afterDelay:.5];
});
});
dispatch_async(queue, ^{
sleep(2.0f);
});
dispatch_async(queue, ^{
dispatch_sync(dispatch_get_main_queue() ^{
[self performSelector:#selector(startNotePlay:) withObject:button afterDelay:1.5];
});
});
There are undoubtedly better ways to accomplish your goal, but without more information, i.e. source code, it's hard to provide a better solution.

Related

stringByEvaluatingJavaScriptFromString causing UI freeze

I am calling JavaScript using stringByEvaluatingJavaScriptFromString. I have UISlider and other UI elements working simultaneously. My whole UI freezes when JavaScript call happens. I looked around about this and found out that making
the call asynchronous should solve the problem.
I have tried two approaches:
[webView performSelectorOnMainThread:#selector(stringByEvaluatingJavaScriptFromString:) withObject:func waitUntilDone:NO]
And this:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
[webView performSelectorOnMainThread:#selector(stringByEvaluatingJavaScriptFromString:) withObject:func waitUntilDone:NO];
});
But none of this seems to work. My UI still freezes for fraction of a second which is noticeable. What am I missing?
Try this:-
dispatch_async(dispatch_get_main_queue(), ^{
[webView stringByEvaluatingJavaScriptFromString:func];
});
performSelectorOnMainThread: executes on main thread (The thread of UI).
I think error in your JavaScript, the script executes infinitely.

MBProgressHUD is not shown on time when main thread is blocked

I'm developing a game that uses cocos2d-x, I'm having a problem on old devices such as ipad 1 where large scene takes a lot of time to load.
so the scene transition can take a few seconds, therefore I tried to implement a "busy" animation between scene transitions while the new scene is being loaded.
I implemented this using MBProgressHUD on IOS and ProgressDialog on android.
I decided that I don't want to start showing this animation immediately, instead I could schedule the animation to start 1-2 seconds after the scene transition starts, so that on newer devices the animation will not be shown at all.
Initially what I did was this:
- (void) showProgressDialog: (int) runWithoutDelay
{
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(showProgressDialogAfterDelay) object:nil];
shouldShow = YES;
if (runWithoutDelay){
[self showProgressDialogAfterDelay];
}
else{
[self performSelector:#selector(showProgressDialogAfterDelay) withObject:nil afterDelay:delay];
}
}
- (void) showProgressDialogAfterDelay
{
if (shouldShow){
isShown = YES;
[progressHUD show:YES];
}
}
and if I pass the scene transition part I would just set shouldShow flag to false and won't start the animation.
The problem is that because cocos2d-x scene transition is done in main\ui thread sometimes instead of calling the show method after 2 seconds it takes up to 6-8 seconds for it to be called and sometimes it even gets called after I set my flag to false.
As I understand it happens because performSelector (and so is NSTimer which I also tried) both run on the same thread by placing the call in the thread run loop queue.
I needed something like performSelectorInBackground that takes delay, so I tried using dispatch_after (even though I still haven't figure out how this could be canceled, as I need to cancel a previous schedule when I create a new one) this looked more accurate according Xcode's logs but even though the logs said that the method was called exactly 2 seconds after being scheduled the time would take 5-8 seconds to show and sometimes would not show at all.
As I understand it, and correct me if I'm wrong, this happens because MBProgressHUD changes to UI must happen on main\UI thread so even though I call [ProgressHUD show:YES] on a background thread the actual update of the UI is scheduled to be executed somehow on the main thread and because of it being stuck on cocos2d-x stuff it only starts to show after that, when the scene transition is completed and its too late.
is there any way around this problem? can I somehow schedule it to start with a delay but have it display right when I want it?
What I don't understand is why if I start it with no delay the animation works smoothly without being stuck even though the main thread is busy with cocos2d-x processing.
I‘ve managed a way around this somehow!
Create a normal method where you will call the progressHUD and call the second
Create the second method where you do the time consuming stuff (loading views)
Perform that method on the main thread
Sample:
-(void)callHUD {
[progressHUD show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performSelectorOnMainThread:#selector(loadView) withObject:nil waitUntilDone:YES];
dispatch_async(dispatch_get_main_queue(), ^{
[progressHUD dismiss];
});
});
}
-(void)loadView {
//Perform your segue or transition which needs to load
}
Hope that could help you a little.

Xcode load slow things after the View is visible

I have a very annoying problem. I have a ViewController with an UIImageView in it. The UIImageView should display a slide show. The images for the are coming from NSURL, so it takes a bit of time.
- (void)viewDidLoad {
[super viewDidLoad];
[self loadImages];
}
This is how I get the Images from the NSURL. The problem I have is, that while the Images are loading I only see a black screen.
At the beginning and at the end of the -(void)loadImages method I implemented a UIActivityIndicator to display the loading time.
I already tried -(void)viewWillAppear and -(void)viewDidLayoutSubviews but nothing worked.
Thanks for help
Jannes
What you need to do is load your images asynchronously and populate them as they come in (with delegate or block callbacks, depending on the API you use). You should absolutely never run networking code, or any other potentially long-running operation, synchronously on the main thread. The reason for this is that the UI runs on the main thread, so if you block it with your own operations, the UI cannot update and your app will become unresponsive.
Whether you're using Apple's networking frameworks or something like AFNetworking (highly recommended), there are many ways to do networking asynchronously with minimal work.
You're probably downloading your images synchronously. Try wrapping it in a dispatch_async block to run it on a different thread.
Example:
-(void)loadImages {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
// download the images here
dispatch_async(dispatch_get_main_queue(), ^(void) {
// update the UI on the main thread
});
});
}
Check out the SDWebImage library as well as most of this heavy lifting is done for you.

UIViewController didn't present immediately

I have
[self presentViewController:viewController animated:YES completion:^{}];
And view controller presents after 5-6 seconds after i click the button first time only. After first time presenting latter works ok.
What seems to be the problem?
Thanks
I guess there is some url download process has been started in ViewController viewDidLoad method, which is eventually blocking till the download has been completed. Though it works second time fine, because of cache, and it downloads faster than first time.
I suggest you to use dispatch block like below
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//Download Code
[self download];
});
or, alternatively you can create a function say
-(void)download{
}
and call it using
[self performSelector:#selector(download) withObject:nil afterDelay:.1];
all in viewDidLoad.
All will work without any problem.
And too you can choose to add UIActivityIndicator to show user an activity for download.
Hope it helps.
Cheers.
Not only the awakeFromNib issue said by #rckoenes, but also check for any heavy load processes in viewDidLoad of presentingViewController as those processes will run on main thread. The heavy load processes may be
1. Downloading an image and fixing it to image,
2. Drawing any high clarity image,
3. Downloading the data from internet (Ex: [NSData dataWithContentsOfURL:url])etc.
This can be avoided by using threads.
If this is not the case, please let me know what is the process you wanted to run in viewDidLoad of presentingViewController.
Are you sure this is happening on the main thread?
You can confirm as follows:
if(! [NSThread isMainThread] )
{
dispatch_async( dispatch_get_main_queue(),
^{
// Continue here
} );
}

UIWebView stringByEvaluatingJavaScriptFromString hangs on iOS5.0/5.1 when called using GCD

I have the following code in viewDidLoad, which works properly on iOS 4.3, but it hangs on iOS 5/5.1. On iOS 5/5.1, the alert dialog is shown but can not be dismissed, the UI thread freezes, the OK button just can not be clicked.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_sync(dispatch_get_main_queue(), ^{
[self.webview stringByEvaluatingJavaScriptFromString:#"alert('HELLO WORLD!')"];
});
});
Is this a bug?
After test, I consider it as a Bug, and changing code to use
[webView performSelectorOnMainThread:#selector(stringByEvaluatingJavaScriptFromString:) withObject:js waitUntilDone:NO]
will solve it.
Try it in - (void)viewDidAppear:(BOOL)animated instead
Why are you using GCD at all here?
Remember that in GCD there is no guarantee which thread your code will called on, regardless of queue. My guess is that your DISPATCH_QUEUE_PRIORITY_DEFAULT code happens to be running on the main thread, which is then triggering a deadlock on your dispatch_sync() call. This could be verified by examining your thread states in the debugger or Instruments.
Which gets back to my original question: what do you get by doing these convolutions instead of just calling the method directly on the main thread? You are going to block either way.
You can also change your dispatch_sync() to dispatch_async() - that should also suppress a deadlock, but should only be done if you have other concurrency needs and don't need a background thread to block.
Try
dispatch_async(dispatch_get_main_queue(), ^{
[self.webview stringByEvaluatingJavaScriptFromString:#"alert('HELLO WORLD!')"];
});

Resources