Recursive call within a block completion in animateWithDuration?
I have a set of animations that need to operate sequentially w开发者_如何学运维ith various checking done at each step. Because the size of the set is determined at run time, I expected to utilize a recursive call... but I am having trouble getting it to function within the 'block' paradigm.
The result is an EXEC_BAD_ACCESS regardless of whether I predeclared the block using
__block void (^myBlock)(BOOL) = ^(BOOL finished){ if (finished) [self nextStep];};
or not, as seen in the following code snippet. Upon debugging, it appears that the 'self' variable is indeed valid.
NSEnumerator* stepEnumerator;
-(void) mainRoutine {
stepEnumerator = [myArray objectEnumerator];
[self nextStep];
}
-(void) nextStep {
id obj;
if ((obj = [stepEnumerator nextObject])) {
// Do my checking at each location
....
// we have another spot to move to
[UIView animateWithDuration:animationDuration
animations:^{self.frame = newFrame;}
completion:^(BOOL finished){ if (finished) [self nextStep];}];
}
}
else {
// we are done, finish house cleaning
}
}
Any help is greatly appreciated.
Thanks!The answer to the question posed is, yes, recursive calls within a block completion are valid.
@BillBrasky brought up a good point about the block losing scope. I do not know enough to say if this is required or not as I have not found it to be an issue with my situation. Everything appears to work correctly for me on each successive iteration through my recursive function.
The core issue with the code as I originally wrote it and submitted it is the use of the FastEnumerator. This is DEFINITELY lost when you leave the current function and venture out into another event loop / new section of the stack frame. I realized as I thought more about it that there is probably quite a bit going on behind the scenes to make FastEnumeration work and it is quite logical that leaving the method would destroy the setup.
As a fix, I replaced the NSEnumerator with a simple integer that I then increment each time through the recursive function. I am not a big fan of this as it could lead to Out of Bounds style issues where as the FastEnumerator will not, nor will for (obj in array)
, but I don't know of another solution. I think I will post that as a separate question...
Corrected code:
int index;
-(void) mainRoutine {
index = 0;
if (index < [myArray count]) {
[self nextStep];
}
}
-(void) nextStep {
// obtain the object from the array
id obj = [myArray objectAtIndex:index++];
// do my checking on the object
...
[UIView animationWithDuration:animationDuration
animations:^{self.frame = [view frame];}
completions:^(BOOL finished) {
if (finished && index < [myArray count]) {
[self nextStep];
}
else {
// We are done, clean up
...
}
}];
}
Thanks again @BillBrasky, you helped point me down the correct path to resolve this. I was too focused on the recursion and my quick analysis of my 'self' object looked fine because everything except for one item was fine. Couple that with the debugger breaking on the block, not the actual offending line and who knows how long I would have been staring at the code without seeing the real issue.
Cheers.
I'm new to blocks too, but I just got done with a similar issue. In my case, the EXEC_BAD_ACCESS was caused because the block had gone out of scope. I suspect that sometime in your 2nd recursion, the block gets created with an odd stack frame because it's executing inside another block.
My solution was to keep the blocks in a property marked copy, e.g.
@property (nonatomic,copy) BOOL (^callback)(DownloadProgress*);
This ensures that everything is retained in a copy in case the original block object goes out of scope and is GC'd.
精彩评论