Селектор цели CADisplayLink срабатывает после его аннулирования - PullRequest
9 голосов
/ 21 июня 2011

У меня есть CADisplayLink, который запускает метод draw в объекте Director.Я хочу аннулировать CADisplayLink и затем освободить некоторые одноэлементные объекты Cache, которые используются объектом Director.Объекты Singleton Cache не сохраняются методом draw.

В методе, называемом stopAnimation в Director (этот метод не связан с методом draw), я делаю:

[displayLink invalidate];

и затем я начинаю освобождать объекты Cache Singleton, но затем запускается CADisplayLink и метод draw вызывается в последний раз.Методы draw пытаются получить доступ к освобожденным одноэлементным объектам, и все происходит сбой.

Это происходит только иногда: в некоторых случаях приложение не падает, поскольку объекты Cache освобождаются после того, как displayLink фактически аннулировани метод рисования уже завершил работу.

Как я могу проверить, после аннулирования displayLink, что метод рисования завершил работу и что он не будет запускаться снова, чтобы так безопасно аннулировать объекты 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 признан недействительным, что метод рисования завершен и что он больше не будет запускаться.

Ответы [ 2 ]

8 голосов
/ 30 ноября 2011

Это может быть немного поздно, но ответов не было ...

Я не думаю, что ваш селектор вызывается еще раз, скорее нить отображаемой ссылки находится в середине вашего метода рисования фрейма. В любом случае, проблема совершенно та же. Это многопоточность, и попытка освободить некоторые объекты в одном потоке, используя их в другом, обычно приводит к конфликту.

Вероятно, самым простым решением было бы поместить флаг и выражение "if" в методе рисования фрейма как

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

, а затем, где бы вы ни делали недействительной вашу ссылку для отображения, установите для "schaduledForDestruction" значение YES.

Если вы действительно думаете, что ссылка для отображения снова вызывает этот метод, вы можете использовать другую, если внутри этого "destroyInProgress".

Если вы не хотите изменять метод рисования фрейма, вы можете попробовать принудительно установить новый селектор для ссылки на отображение ...

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];
}
0 голосов
/ 17 октября 2017

Проблема в том, что CALayer display() продолжает вызываться даже после выхода CADisplayLink из режима цикла выполнения.

Если таймер срабатывает, когда цикл выполнения находится в середине выполнения процедуры-обработчика, он ожидает следующего цикла цикла выполнения, чтобы вызвать его процедуру-обработчик.

Самый надежный способ предотвратить обновление слоя после вызова invalidate() - это создать подкласс CALayer, добавить флаг, изменить флаг вместе с invalidate() и проверить значение флага перед вызовом super.display().

class Layer: CALayer {

    var shouldDisplay: Bool = true

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

Итак, в связи с аннулированием вашей ссылки на дисплей, установите для слоя shouldDisplay значение false. Это не позволит подклассу продолжать перезагружать содержимое слоя, независимо от того, какой поток вызывает invalidate().

...