9

我有一个触发drawDirector 对象中的方法的 CADisplayLink。我想使 CADisplayLink 无效,然后取消分配 Director 对象使用的一些单例缓存对象。该draw方法不保留单例缓存对象。

在 Director 中调用的一个方法stopAnimation中(此方法与方法无关draw),我这样做:

[displayLink invalidate];

然后我开始释放单例缓存对象,但随后 CADisplayLink 触发并且该draw方法最后一次被调用。这些draw方法尝试访问已释放的单例对象并且一切都崩溃了。

这只有时会发生:有时应用程序不会崩溃,因为在 displayLink 实际无效并且 draw 方法已经完成运行之后释放了 Cache 对象。

在使 displayLink 无效后,如何检查 draw 方法是否已完成运行并且它不会再次触发,以便安全地使 Cache 对象无效?如果可能的话,我不想修改draw方法。

我尝试了多种组合,包括displayLink invalidate在主线程上使用

[self performSelectorOnMainThread:@selector(stopAnimation) withObject:self waitUntilDone:YES]

或尝试通过使用在 currentRunLoop 中执行它

[[NSRunLoop currentRunLoop] performSelector:@selector(stopAnimation) target:self argument:nil order:10 modes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];

但结果总是一样的,有时它会过早释放共享缓存。

我也不想performSelector:withObject:afterDelay:随意延迟使用该方法。我想确保 displayLink 无效,draw 方法结束,并且不会再次运行。

4

2 回答 2

8

这可能有点晚了,但由于没有答案......

我不认为,您的选择器被再次调用,而是显示链接的线程位于您的并条框方法的中间。在任何情况下,问题都是一样的。这是多线程,通过尝试在一个线程中释放某些对象而在另一个线程中使用它们通常会导致冲突。

可能最简单的解决方案是在您的并条框方法中放置一个标志和一个“if语句”作为

if(schaduledForDestruction) {
[self destroy];
 return;
}

然后无论您在何处使显示链接无效,都将“schaduledForDestruction”设置为“是”。

如果你真的认为显示链接再次调用了 tis 方法,你可以在那个“destructionInProgress”中使用另一个。

如果您不想更改并条框方法,您可以尝试将新的选择器强制到显示链接...

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);
}

或者只是考虑这样的事情(但我不能说这是安全的。如果新创建的显示链接可以在与原来不同的线程上运行选择器,那么它什么也解决不了)..

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];
}
于 2011-11-30T15:52:16.543 回答
0

问题是即使从运行循环模式中释放CALayer'sdisplay()也会继续被调用。CADisplayLink

如果在运行循环正在执行处理程序例程时触发计时器,则计时器将一直等到下一次通过运行循环调用其处理程序例程。

防止层在调用后更新的最面向未来的方法invalidate()是子类CALayer化、添加标志、更改标志invalidate()并在调用之前检查标志的值super.display()

class Layer: CALayer {

    var shouldDisplay: Bool = true

    override func display() {
        if shouldDisplay {
            super.display()
        }
    }
}

因此,在使显示链接无效的同时,将图层设置shouldDisplay为 false。这将阻止子类继续重新加载图层的内容,而不管哪个线程调用invalidate().

于 2017-10-16T21:48:48.060 回答