I am using BFTasks to perform some SpriteKit drawing in the background, but I'm not sure I'm using them correctly, as the drawing is locking up the main thread.
Each object is made up of several SKSpriteNodes, that are flattened before rendering. I'd like each one to render as soon as it's been flattened, i.e. when I call [self addChild:miniNode];
But it waits until all have been created, (locking the main thread) and then they appear all at once.
I've simplified my code below to show the chain of tasks:
- (void)drawLocalRelationships
{
[ParseQuery getLocalRelationships:_player.relationships block:^(NSArray *objects, NSError *error) {
[[[self drawRelationships:objects forMini:_player]
continueWithBlock:^id(BFTask *task) {
//this continues once they've all been drawn and rendered
return nil;
}];
}];
}
- (BFTask *)drawRelationships:(NSArray *)relationships forMini:(Mini *)mini
{
return [_miniRows drawSeriesRelationships:relationships forMini:mini];
}
The MiniRows class:
- (BFTask *)drawSeriesRelationships:(NSArray *)relationships forMini:(Mini *)mini
{
BFTask *task = [BFTask taskWithResult:nil];
for (Relationship *relationship in relationships) {
task = [task continueWithBlock:^id(BFTask *task) {
return [self drawRelationship:relationship mini:mini];
}];
}
return task;
}
- (BFTask *)drawRelationship:(Relationship *)relationship mini:(Mini *)mini
{
//code to determine 'row'
return [row addMiniTask:otherMini withRelationship:relationship];
}
The Row class:
- (BFTask *)addMiniTask:(Mini*)mini withRelationship:(Relationship *)relationship
{
//drawing code
MiniNode *miniNode = [self nodeForMini:mini size:size position:position scale:scale];
[self addChild:miniNode]; //doesn't actually render here
return [BFTask taskWithResult:nil];
}
I've tried running the addMiniTask method on a background thread, but it doesn't seem to make a difference. I wonder if I'm misunderstanding the concept of BFTasks - I figured they're automatically run on a background thread, but perhaps not?
BFTasks are NOT run on a background thread by default !
If you do:
immediateTask completes, i.e. the completed property is YES, immediately in the current thread.
Also, if you do:
Once task completes, the block is executed in the default executor, which executes blocks immediately in the current thread unless the call stack is too deep in which case it is offloaded to a background dispatch queue. The current thread being the one where continueWithBlock is called. So unless you're calling the previous code in a background thread, the long running operation will block the current thread.
However, you can offload a block to a different thread or queue using an explicit executor:
Choosing the right executor is critical:
Depending on executor you will get different behaviour.
The advantage of BFTasks is that you can chain and synchronise tasks running in different threads. For example, to update the UI in the main thread after long running background operation, you would do:
PFQuery findInBackgroundWithBlock method executes the block with the default executor, so if you call that method from the main thread, there is a great chance that the block will also execute in the main thread. In your case, although I know nothing about SpriteKit, I would fetch all sprites, then update the UI:
But with this solution, all sprites are fetch (flattened ?) then the UI update. If you want to update the UI, every time a sprite is fetched, you could do something like this:
Here the middle task create a task that will complete once all renderUpdate tasks have completed.
Hope this help.
B