开发者

CADisplayLink target selector being triggered after it is invalidated

开发者 https://www.devze.com 2023-03-14 11:22 出处:网络
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. T

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().

0

精彩评论

暂无评论...
验证码 换一张
取 消