CADisplayLink target selector being triggered after it is invalidated
I have a CADisplayLink that triggers a draw
method in a Director object. I want to invalidate the CADisplayLink and then deallocate some singleton Cache objects that are used by the Director object. The singleton Cache objects are not retained by the draw
method.
In a method called st开发者_如何学PythonopAnimation
in the Director (this method is unrelated to the draw
method), I do:
[displayLink invalidate];
and then I start releasing the singleton Cache objects, but then the CADisplayLink fires and the draw
method gets called one last time. The draw
methods tries to access the deallocated singleton objects and everything crashes.
This only happens sometimes: there are times in which the app doesn't crash because the Cache objects are released after the displayLink is actually invalidated and the draw method has already finished running.
How can I check, after invalidating the displayLink, that the draw method has finished running and that it won't fire again, in order so safely invalidate the Cache objects? I don't want to modify the draw
method if possible.
I tried a number of combinations, including performing displayLink invalidate
on the main thread using
[self performSelectorOnMainThread:@selector(stopAnimation) withObject:self waitUntilDone:YES]
or trying to perform it in the currentRunLoop by using
[[NSRunLoop currentRunLoop] performSelector:@selector(stopAnimation) target:self argument:nil order:10 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];
but the results is always the same, sometimes it releases the shared Caches too early.
I also don't want to use the performSelector:withObject:afterDelay:
method with an arbitrary delay. I want to make sure the displayLink is invalidated, that the draw method ended, and that it won't be run again.
This might be a bit late but since there has been no answers...
I do not think, your selector is called once more, but rather the display link's thread is in the middle of your draw frame method. In any case, the problem is quite the same.. This is multithreading and by trying to dealloc some objects in one thread while using them in another will usually result in a conflict.
Probably the easiest solution would be putting a flag and an "if statement" in your draw frame method as
if(schaduledForDestruction) {
[self destroy];
return;
}
and then wherever you are invalidating your display link set "schaduledForDestruction" to YES.
If you really think the display link calls tis method again, you could use another if inside that one "destructionInProgress".
If you do not want to change the draw frame method, you could try forcing a new selector to the display link...
CADisplayLink *myDisplayLink;
BOOL resourcesLoaded;
SEL drawSelector;
- (void)destroy {
if(resourcesLoaded) {
[myDisplayLink invalidate];
//free resources
resourcesLoaded = NO;
}
}
- (void)metaLevelDraw {
[self performSelector:drawSelector];
}
- (void)drawFrame {
//draw stuff
}
- (void)beginAnimationing {
drawSelector = @selector(drawFrame);
myDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(metaLevelDraw)];
[myDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)endAnimationing {
drawSelector = @selector(destroy);
}
or just consider something like this (but I can't say this is safe. If the newly created display link can run the selector on a different thread then the original, it solves nothing)..
CADisplayLink *myDisplayLink;
BOOL resourcesLoaded;
- (void)destroy {
if(resourcesLoaded) {
[myDisplayLink invalidate];
//free resources
resourcesLoaded = NO;
}
}
- (void)drawFrame {
//draw stuff
}
- (void)beginAnimationing {
myDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawFrame)];
[myDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
- (void)endAnimationing {
[myDisplayLink invalidate];
myDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(destroy)];
[myDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
The problem is that CALayer
's display()
continues to be called even after the CADisplayLink
is released from the run loop mode.
If a timer fires when the run loop is in the middle of executing a handler routine, the timer waits until the next time through the run loop to invoke its handler routine.
The most future-proof way to prevent the layer from updating after calling invalidate()
is to subclass CALayer
, add a flag, change the flag alongside invalidate()
, and check the flag's value before calling super.display()
.
class Layer: CALayer {
var shouldDisplay: Bool = true
override func display() {
if shouldDisplay {
super.display()
}
}
}
So, in conjunction with invalidating your display link, set the layer's shouldDisplay
to false. This will prevent the subclass from continuing to reload the contents of the layer, regardless of which thread calls invalidate()
.
精彩评论