Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 stopAnimation 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.

like image 974
Ricardo Sanchez-Saez Avatar asked Jun 20 '11 23:06

Ricardo Sanchez-Saez


1 Answers

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];
}
like image 50
Matic Oblak Avatar answered Nov 04 '22 03:11

Matic Oblak