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();
}
});
});
}
Related
I amy trying to make simple progress bar in SpriteKit. To simplify the example I will use SKLabelNode and it's text property, which will indicate the progress.
Here is the code( GameScene.m ):
#import "GameScene.h"
#interface GameScene ()
typedef void (^CompletitionHandler)(void);
#property (nonatomic,strong)SKLabelNode* progressBar;
#end
#implementation GameScene
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
self.progressBar = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
self.progressBar.fontColor = [SKColor redColor];
self.progressBar.fontSize = 24.0;
self.progressBar.position = CGPointMake(CGRectGetMidX(self.frame),CGRectGetMidY(self.frame));
[self addChild:self.progressBar];
[self loadSceneAssetsWithCompletionHandler:^{
[self setupScene:view];
}];
}
- (void)loadSceneAssetsWithCompletionHandler:(CompletitionHandler)handler {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// Load the shared assets in the background.
//This part simulates bunch of assets(textures, emitters etc) loading in background
for (int i = 0; i <= 100; i++) {
[NSThread sleepForTimeInterval:0.01];
float progressValue = (float)i;
dispatch_async(dispatch_get_main_queue(), ^{
self.progressBar.text = [NSString stringWithFormat:#"%1.f", progressValue];
});
}
if (!handler) {
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
// Call the completion handler back on the main queue.
handler();
});
});
}
Now I am trying to find an obvious way, if there is any, to update the progress based on percentage of data loaded from background queue.
The problem is that I want to show percentage of loaded data from 0 to 100. But I don't know how to tell what would be 1% of bunch of textures, emitters and nodes, or I can say, I don't know how to update a progress bar after %1 is loaded. This is because I am working with different kind of objects. Is there any way to check the state of certain background queue to see how much stuff is left to be executed(loaded)?
Anybody has any idea, or suggestion ?
If you are looking to get an update on, for example, how much is left to load for a texture atlas then the answer is you can't.
You can however keep a "load items" counter and update that once an asset is loaded. For example, you have 13 texture atlases to load and some sound files, both of those have completion capability in their loading methods.
- (void)preloadWithCompletionHandler:(void (^)(void))completionHandler
playSoundFileNamed:(NSString *)soundFile
waitForCompletion:(BOOL)wait
Every time an asset finishes loading, update your counter. To be honest though, I am not sure if this is really necessary as loading usually happens very quickly. Display a generic "loading" message for a few seconds is probably your best (and easiest) option.
Adding the following code to a new project cause a flash of a black screen. It seems to be caused by the fade out animation of the splash screen. Unfortunately, the long process must be on main thread. It is possible to avoid it by delaying it but it is unreliable and lengthens the loading process.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// A spinner is shown
// This most probably will not cause a black screen
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// [self loadSomething];
// });
// This will not cause a black screen but not suitable for my use
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// [self loadSomething];
// });
dispatch_async(dispatch_get_main_queue(), ^{
[self loadSomething];
});
}
- (void)loadSomething
{
NSLog(#"start long process");
NSDate *start = [NSDate date];
while (-[start timeIntervalSinceNow] < 5) {
}
}
Consider why it must be on the main thread and come up with a way to move this processing or minimise it.
You should create a root view controller which displays the app default image and have that view controller displayed (probably with some animation or progress indication) until your processing has completed.
If you're already on your main thread, then there is no need of getting the main thread asynchronously.
dispatch_async(dispatch_get_main_queue(), ^{
});
The above code should be removed.
In case you want to do something or load something, then you should use
performSelectorInBackground
Hope this helps...
I'm trying to update my textView on screen before it starts downloading data. Right now, it only updates the view after all of the downloads are complete. How can I do it before or in between the downloads?
Edit: I want the self.textView.text = #"Connection is good, start syncing..."; to update the UI before the downloading starts. But right now, it only updates after the download finishes.
Here is what the code looks like.
if ([self.webApp oAuthTokenIsValid:&error responseError:&responseError]) {
self.textView.text = #"Connection is good, start syncing...";
[self.textView setNeedsDisplay];
[self performSelectorInBackground:#selector(downloadCustomers:) withObject:error];
}
I'm new to this and have yet to learn how threads work, but from what I read, the downloadCustomers function should be using a background thread leaving the main thread to update the UI.
if ([self.webApp oAuthTokenIsValid:&error responseError:&responseError]) {
self.textView.text = #"Connection is good, start syncing...";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self downloadCustomers];
dispatch_async(dispatch_get_main_queue(), ^{
//Do whatever you want when your download is finished, maybe self.textView.text = #"syncing finished"
});
});
}
The pattern here is to initialize your download on background thread and then call back to main thread for UI update.
Below is an example using GCD. The advantage of GCD version is that you can consider using whatever you do in -downloadCustomers, to insert in-line where you call it.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self downloadCustomers];
dispatch_async(dispatch_get_main_queue(), ^{
[self.textView setNeedsDisplay];
});
});
I have a method like:
- (BOOL)shouldDoSomeWork {
BOOL result = // here I need do hard work with data in background thread and return result, so main thread should wait until the data is calculated and then return result;
return result;
}
How to implement that?
Are you looking for this:
-(void) startWork
{
//Show activity indicator
[NSThread detachNewThreadSelector:#selector(doSomeWork) toTarget:self withObject:nil];
}
-(void) doSomeWork
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
//Do your work here
[pool release];
[self performSelectorOnMainThread:#selector(doneWork) withObject:nil waitUntilDone:NO];
}
-(void) doneWork
{
//Hide activity indicator
}
Example how to do it with GCD:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Your hard code here
// ...
//BOOL result = ...
dispatch_async(dispatch_get_main_queue(),^{
[self callbackWithResult:result]; // Call some method and pass the result back to main thread
});
});
That's not typically how you would do it. You need something structured more like this:
- (void)doSomeWorkAndThen:(^void)block {
dispatch_async(dispatch_get_global_queue(0, 0), ^ {
// do
// some
// work
dispatch_sync(dispatch_get_main_queue(), ^ {
block();
});
});
That is, you keep the request and what you do afterwards in one place.
Common advice is to use the highest level of abstraction available to you to perform a task. As such NSThread should be relatively low down in the list of things you can do to execute work in the background.
The order you investigate APIs should be like this:
NSOperation / NSOperationQueue
Grand Central Dispatch (libdispatch)
NSThread
POSIX threads
With the first two you write your code as a "unit of work" and then put this work on a queue to be executed at some point. The system takes care of creating and destroying threads for you and the APIs are easy to work with. Here's an example using NSOperationQueue.
NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
//Do work
//update your UI on the main thread.
[self performSelectorOnMainThread:#selector(workDone:) withObject:workResults waitUntilDone:NO];
}];
[self.operationQueue addOperation:blockOperation];
easy as that.
I have implemented the following NSOperation, to draw N custom views
- (void)main {
for (int i=0; i<N; i++) {
<< Alloc and configure customView #i >>
//(customView is a UIView with some drawing code in drawrect)
[delegate.view addSubview:customView];
}
NSLog(#"Operation completed");
}
in the drawRect method of the customView I have
- (void)drawRect {
<<Drawing code>>
NSLog(#"Drawed");
delegate.drawedViews++;
if (delegate.drawedViews==VIEWS_NUMBER) {
[delegate allViewsDrawn];
}
}
So the delegate get the notification when all the views are drawn.
The problem is that after the "Operation completed" log it takes about 5 seconds before I can see the first "Drawed" log.
Why is this happening? And generally speaking, how should I behave in order to find out which line of code is taking so much time being executed?
------ EDIT ------
Sometimes (like 1 out of 10 times) I was getting crashes doing this because I shouldn't call addsubview from the NSOperation since it is not thread-safe. So I changed it to:
[delegate.view performSelectorOnMainThread:#selector(addSubview:) withObject:customView waitUntilDone:NO];
Now I don't have crashes anymore, but the process takes a very long time to be executed! Like 5 times more than before.
Why is it so slow?
To make things work properly we need to forget about NSOperation and use this "trick"
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(main_queue, ^{
[self createCustomViews];
dispatch_async(main_queue, ^{
[self addAnotherCustomViewToView];
});
});