I have a GCD drawing queue to update my OpenGL ES scene which is structured like this:
- (void)drawFrame {
dispatch_async(drawingQueue, ^{
if (dispatch_semaphore_wait(frameRenderingSemaphore, DISPATCH_TIME_NOW) != 0) {
return;
}
#autoreleasepool {
[self startDrawing];
// drawing code
[self endDrawing];
}
dispatch_semaphore_signal(frameRenderingSemaphore);
});
}
When the app resigns active or enters background (both) I stop the OpenGL drawing run loop by invalidating the CADisplayLink.
The problem however is that dispatch_asyn dispatches a drawing block even until after the CADisplayLink got invalidated. When the user presses the home button, my app crashes because it attempted to draw a frame in OpenGL even though iOS teared down the context already.
Is there a way to kill / pause a GCD queue so it doesn't dispatch anything anymore?
I think the easiest way would be to have a flag in your application that you check before executing the block. For example:
- (void)drawFrame {
dispatch_async(drawingQueue, ^{
if (appIsTerminated || appIsInBackground) {
return;
}
if (dispatch_semaphore_wait(frameRenderingSemaphore, DISPATCH_TIME_NOW) != 0) {
return;
}
#autoreleasepool {
[self startDrawing];
// drawing code
[self endDrawing];
}
dispatch_semaphore_signal(frameRenderingSemaphore);
});
}
You can set those values in your app delegate in applicationDidEnterBackground and applicationWillTerminate.
You could also try this:
dispatch_suspend(drawingQueue);
dispatch_release(drawingQueue);
Not quite sure about those though.
Here's all the details: https://developer.apple.com/library/mac/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html
Related
I implemented login method in this way:
[KVNProgress show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//some error handling like:
if ([_usernameField.text length] < 4) {
[KVNProgress showErrorWithStatus:#"Username too short!"];
_passwordField.text = #"";
return;
}
//Then I call login web service synchronously here:
result = [ServerRequests login];
dispatch_async(dispatch_get_main_queue(), ^{
if(!result)
{
[KVNProgress showErrorWithStatus:#"problem!" completion:NULL];
_passwordField.text = #"";
}
else if([result.successful boolValue])
{
[KVNProgress showSuccessWithStatus:result.message];
}
});
});
It crashed mostly and by surrounding blocks with only Main Queue (no priority default one) that solved! but the problem is:KVNProgress is only showing in error handling area not the next part that we call web service. It's not user friendly at all! Any idea is welcomed :)
You MUST call methods that update the user interface in any way from the main thread, as per the UIKit documentation:
For the most part, use UIKit classes only from your app’s main thread. This is particularly true for classes derived from UIResponder or that involve manipulating your app’s user interface in any way.
I suggest you try to limit the number of callbacks you make to the main thread, so therefore you want to batch as much user interface updates together as you can.
Then all you have to do, as you correctly say, is to use a dispatch_async to callback to your main thread whenever you need to update the UI, from within your background processing.
Because it's asynchronous, it won't interrupt your background processing, and should have a minimal interruption on the main thread itself as updating values on most UIKit components is fairly cheap, they'll just update their value and trigger their setNeedsDisplay so that they'll get re-drawn at the next run loop.
From your code, it looks like your issue is that you're calling this from the background thread:
if ([_usernameField.text length] < 4) {
[KVNProgress showErrorWithStatus:#"Username too short!"];
_passwordField.text = #"";
return;
}
This is 100% UI updating code, and should therefore take place on the main thread.
Although, I have no idea about the thread safety of KVNProgress, I assume it should also be called on the main thread as it's presenting an error to the user.
Your code therefore should look something like this (assuming it's taking place on the main thread to begin with):
[KVNProgress show];
//some error handling like:
if ([_usernameField.text length] < 4) {
[KVNProgress showErrorWithStatus:#"Username too short!"];
_passwordField.text = #"";
return;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Then I call login web service synchronously here:
result = [ServerRequests login];
dispatch_async(dispatch_get_main_queue(), ^{
if(!result) {
[KVNProgress showErrorWithStatus:#"problem!" completion:NULL];
_passwordField.text = #"";
} else if([result.successful boolValue]) {
[KVNProgress showSuccessWithStatus:result.message];
}
});
});
I am trying to figure out what is the difference between these two examples and how preloadTextureAtlases :withCompletionHandler works. Here is the code:
//GameScene.m
-(void)didMoveToView:(SKView *)view {
//First I create an animation, just a node moving from one place to another and backward.
//Then I try to preload two big atlases
[SKTextureAtlas preloadTextureAtlases:#[self.atlasA, self.atlasB] withCompletionHandler:^{
[self setupScene:self.view];
}];
I suppose that preloadTextureAtlases doing loading on background thread because my animation is smooth?
But are there any differences(or it can be problematic somehow) to call preloadTextureAtlases from background thread? Like this:
//GameScene.m
- (void)loadSceneAssetsWithCompletionHandler:(CompletitionHandler)handler {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[SKTextureAtlas preloadTextureAtlases:#[self.atlasA, self.atlasB] withCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
[self setupScene:self.view];
});
}];
if (!handler){return;}
dispatch_async(dispatch_get_main_queue(), ^{
handler();
});
});
}
And then call this method from didMoveToView:
[self loadSceneAssetsWithCompletionHandler:^{
NSLog(#"Scene loaded");
// Remove loading animation and stuff
}];
You can preload in the background but the question is why would you? Chances are your game cannot start playing until all required assets are loaded so the user will have to wait regardless.
You can preload whatever assets you need to get the game started and background load any additional assets you might require during later game play. Those kind of circumstances are really only required in very complex games and not on the iOS platform.
As for your question on loading in the background while game play is ongoing, there is no definite answer. It all depends on how big of a load, your CPU load, etc... Try it out and see what happens because it sounds like you are asking questions on issues you have not tried out first.
If you are really intent on background tasks, remember that you can add a completion block to your custom methods like this:
-(void)didMoveToView:(SKView *)view {
NSLog(#"start");
[self yourMethodWithCompletionHandler:^{
NSLog(#"task done");
}];
NSLog(#"end");
}
-(void)yourMethodWithCompletionHandler:(void (^)(void))done; {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// a task running another thread
for (int i=0; i<100; i++) {
NSLog(#"working");
}
dispatch_async(dispatch_get_main_queue(), ^{
if (done) {
done();
}
});
});
}
I'm having this wierd problem with the app freezing at a certain point. I'm guessing its got to do with how I'm using NSConditionLock.
Theres a library I have been given to use, which consists of a series of survey questions, but it works in such a way that it races directly to the last question without accepting answers, hence the need to pause the thread and accept input from the user.
I haven't used it before so maybe someone could help if I'm implementing it wrongly?
Please let me know if the code provided is insufficient.
- (void)viewDidLoad
{
[super viewDidLoad];
//INITIALISE CONDITION LOCK WITH CONDITION 0
condition=[[NSConditionLock alloc]initWithCondition: 0];
}
- (IBAction)startPressed:(UIButton*)sender {
if (sender.tag == 1) {
//START BACKGROUND THREAD
surveyThread = [[NSThread alloc] initWithTarget:self selector:#selector(runProjecttest) object:nil];
[surveyThread start];
}
else
{
//DO SOME STUFF AND THEN UNLOCK
[condition unlockWithCondition:1];
}
}
- (void) runProjecttest:(AbstractTask *)rendertask
{
// DO STUFF AND SHOW UI ON MAIN THREAD, THEN LOCK
[self performSelectorOnMainThread:#selector(showUI:) withObject:task waitUntilDone:YES];
[condition lockWhenCondition: 1];
}
EDIT: In short, I want the Objc equivalent of this java snippet...
this.runOnUiThread(showUI);
try
{
//SLEEP
Thread.sleep(1000*60*60*24*365*10);
}
catch (InterruptedException e)
{
//WAKE
setResponse(at,showUI);
}
EDIT 2: ShowUI method on Paul's request.
[self removePreviousSubViews];
switch ([task getType]) {
case SingleChoiceType:
{
NSLog(#"SingleChoiceType");
isMultipleChoice = NO;
[self addSingleChoiceView:nil];
break;
}
case TextType:
{
NSLog(#"TextType");
self.txtTextType.keyboardType=UIKeyboardTypeDefault;
[self addTextTypeView:nil];
break;
}
...more cases
}
-(void)addTextTypeView:(NSSet *)objects
{
self.txtTextType.text = #"";
CGRect frame = self.txtQuestionType.frame;
// frame.size = [self.txtQuestionType sizeThatFits: CGSizeMake(self.txtQuestionType.frame.size.width, FLT_MAX)];
frame.size.height = [self textViewHeightForAttributedText:self.txtQuestionType.text andWidth:self.txtQuestionType.frame.size.width andTextView:self.txtQuestionType];
self.txtQuestionType.frame=frame;
self.textTypeView.frame = CGRectMake((self.view.frame.size.width - self.textTypeView.frame.size.width)/2, ( self.txtQuestionType.frame.origin.y+self.txtQuestionType.frame.size.height), self.textTypeView.frame.size.width, self.textTypeView.frame.size.height);
[self.view addSubview: self.textTypeView];
}
I agree with BryanChen, I think you may have another issue. Without details on the survey library, it is impossible to confirm, but assuming that it is a UIViewController than accepts touch inputs to progress through a series of questions, it is hard to see why it is a threading issue - it simply shouldn't advance without user interaction.
That aside, your use of NSCondtionLock doesn't look right either.
Essentially an NSConditionLock has an NSInteger that represents the current 'condition', but just think of it of a number. There are then two basic operations you can perform -
lockWhenCondition:x will block the current thread until the 'condition' is 'x' and the lock is available. It will then claim the lock.
unlockWithCondition:y releases the lock and sets the condition to 'y'
There are also methods to set timeouts (lockBeforeDate) and try to claim the lock without blocking (tryLock, tryLockWhenCondition).
To synchronise two threads, the general pattern is
Initialise Lock to condition 'x'
Thread 1 lockWhenCondition:x -This thread can claim the lock because it is x
Thread 2 lockWhenCondition:y - This thread will block because the lock is x
Thread 1 completes work, unlockWithCondition:y - This will enable Thread 2 to claim the lock and unblock that thread
Your code looks strange, because you are starting a thread in your if clause but unlocking in an else clause. I would have thought you would have something like -
-(IBAction)startPressed:(UIButton*)sender {
if (sender.tag == 1) {
//START BACKGROUND THREAD
surveyThread = [[NSThread alloc] initWithTarget:self selector:#selector(runProjecttest) object:nil];
[surveyThread start];
[condition:lockWithCondition:1]; // This will block until survey thread completes
[condition:unlockWithCondition:0]; // Unlock and ready for next time
}
}
- (void) runProjecttest:(AbstractTask *)rendertask
{
// DO STUFF AND SHOW UI ON MAIN THREAD, THEN LOCK
[condition lockWhenCondition: 0];
[self performSelectorOnMainThread:#selector(showUI:) withObject:task waitUntilDone:YES];
[condition unlockWithCondition:1];
}
BUT This looks like a recipe for deadlock to me, because you are performing the showUI selector on the main thread that is blocked waiting for the survey thread to complete.
Which brings us back to the question, what does showUI do and why is it skipping directly to the end?
In my app I have pretty long
- (void)appDidEnterBackground:(NSNotification*)notif method,
it takes 1-2 seconds to execute. This causes the following issue: if I close app and open it again very fast, than
- (void)appWillEnterForeground:(NSNotification*)notif
is being called before -appDidEnterBackground is finished, which leads to crash - data is not consistent, or something like this. Rather than investigate what exactly is wrong in my data, I want to prevent this case from ever happening - I want to wait until appDidEnterBackground is done.
My code:
- (void)appDidEnterBackground:(NSNotification*)notif
{
[self processAppDidEnterBackgroundRoutines];
NSLog(#"%s - end", __FUNCTION__);
}
- (void)processAppDidEnterBackgroundRoutines
{
// 1-2 seconds prosessing
}
- (void)appWillEnterForeground:(NSNotification*)notif
{
NSLog(#"%s - begin", __FUNCTION__);
}
I tried to call
[self performSelectorOnMainThread:#selector(processAppDidEnterBackgroundRoutines) withObject:nil waitUntilDone:YES];
, but it doesn't help for some reason - appWillEnterForeground: is still being called before processAppDidEnterBackgroundRoutines is finished.
Does anyone have others ideas how to synchronise this calls?
Would it work for you to put both in a serial queue?
- (void)appDidEnterBackground:(NSNotification*)notif
{
dispatch_sync( serialQueue, ^()
{
[self processAppDidEnterBackgroundRoutines];
});
}
- (void)processAppDidEnterBackgroundRoutines
{
// 1-2 seconds prosessing
}
- (void)appWillEnterForeground:(NSNotification*)notif
{
dispatch_sync(serialQueue, ^()
{
// do your stuff
});
}
Your problem appears to be because you're using performSelectorOnMainThread: in your appDidEnterBackground: method. This is causing that selector to be run later on, because you're already running on the main thread. Just stop doing performSelectorOnMainThread:, because it's unnecessary, and is what's causing your problem.
In Java you can suspend the current thread's execution for an amount of time using Thread.sleep(). Is there something like this in Objective-C?
Yes, there's +[NSThread sleepForTimeInterval:]
(Just so you know for future questions, Objective-C is the language itself; the library of objects (one of them at least) is Cocoa.)
Sleeping for one second in Java:
Thread.sleep(1000);
Sleeping for one second in Objective C:
[NSThread sleepForTimeInterval:1.0f];
Why are you sleeping? When you sleep, you are blocking the UI and also any background URL loading not in other threads (using the NSURL asynchronous methods still operates on the current thread).
Chances are what you really want is performSelector:withObject:AfterDelay. That's a method on NSObject you can use to call a method at some pre-determined interval later - it schedules a call that will be performed at a later time, but all of the other stuff the thread handles (like UI and data loads) will still continue.
Of course, you could also use the standard Unix sleep() and usleep() calls, too. (If writing Cocoa, I'd stay with the [NSThread sleepForTimeInterval:], however.)
If you use NSThread sleepForTimeInterval(commented code) to sleep, fetching data will be blocked, but +[NSThread sleepForTimeInterval:] (checkLoad method) will not block fetching data.
My example code as below:
- (void)viewDidAppear:(BOOL)animated
{
//....
//show loader view
[HUD showUIBlockingIndicatorWithText:#"Fetching JSON data"];
// while (_loans == nil || _loans.count == 0)
// {
// [NSThread sleepForTimeInterval:1.0f];
// [self reloadLoansFormApi];
// NSLog(#"sleep ");
// }
[self performSelector:#selector(checkLoad) withObject:self afterDelay:1.0f];
}
-(void) checkLoad
{
[self reloadLoansFormApi];
if (_loans == nil || _loans.count == 0)
{
[self performSelector:#selector(checkLoad) withObject:self afterDelay:1.0f];
} else
{
NSLog(#"size %d", _loans.count);
[self.tableView reloadData];
//hide the loader view
[HUD hideUIBlockingIndicator];
}
}
usleep() can also be used as ive used this to pause the current thread at times