Сбой в CALayer -hitTest:

Этот вопрос действительно поставил меня в тупик...

У меня есть проект iPad, в котором я использую UIPanGestureRecognizer, и я использую следующий вызов метода в своем handlePanGesture:

- (AIEnemyUnit *) hitTestForEnemyUnit:(CGPoint)where {
    CALayer * layer = [self hitTest:where];

    while (layer) {
        if ([layer isKindOfClass:[AIEnemyUnit class]]) {
            return (AIEnemyUnit *)layer;
        } else {
            layer = layer.superlayer;
        }
    }

    return nil;
}

Как только я «нахожу» слой AIEnemyUnit, я продолжаю перетаскивание, и все работает нормально. За исключением примерно 6-10-го «перетаскивания», я получаю сбой с отладчиком глубоко внутри только CALayer -hitTest:

modifying layer that is being finalized - 0x124530
*** -[NSCFSet hitTest:]: unrecognized selector sent to instance 0x124530
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', 
reason: 
'*** -[NSCFSet hitTest:]: unrecognized selector sent to instance 0x124530'

person Phil M    schedule 28.09.2010    source источник


Ответы (2)


Судя по симптомам, у вас перевыпуск CALayer.

Две области, которые вы должны проверить:

1) Вы «сохраняете» это CALayer в переменной, не сохраняя его? Если вы используете какие-либо пулы автоматического выпуска (включая тот, который предоставляется в основном потоке), эти слои могут быть выпущены непреднамеренно. Как отмечено в комментариях, поскольку они не выпускаются автоматически, это может произойти без попадания в пул. Однако это может произойти в любое время, когда CALayer освобождается, пока вы держите ссылку.

2) Позже вы явно вызываете релиз на этом уровне. Поскольку вам предоставляется этот слой как есть (и hitTest:, и superlayer возвращают объекты без дополнительного счетчика удержания), вы не являетесь владельцем и, следовательно, не должны освобождать его.

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

person bobDevil    schedule 28.09.2010
comment
Хороший ответ. Одно но: слои, возвращаемые hitTest: и superlayer, не освобождаются автоматически, но и не сохраняются. Это потенциально может привести к сбою: CALayer *sublayer = [[layer sublayers] objectAtIndex:0]; [sublayer removeFromSuperlayer]; [sublayer superlayer]; - person rpetrich; 28.09.2010
comment
Спасибо, я не был уверен, но знал, что они не принадлежат звонившему. Починил это. - person bobDevil; 28.09.2010
comment
Спасибо за исправление! Я переключился с '@synthesize dragItem;' к ручному установщику и забыли установить 'dragItem = [newDragItem keep];'. В других сообщениях я видел, что сообщение об изменении слоя, которое завершается, было связано с проблемами сохранения счетчика, но мои сообщения об ошибках были совершенно другими, и я не был уверен, что это был ответ. Но из-за других сообщений я тщательно просмотрел свой код, чтобы увидеть, сколько раз я вызывал -release и -removeFromSuperlayer. Я был так сосредоточен на аспекте выпуска, что не подумал о сохранении, так что спасибо - person Phil M; 28.09.2010

Я думаю, что на самом деле в документации hitTest есть немного «дезинформации». Я сам столкнулся с аналогичной проблемой, поместив в окно 4 экземпляра подкласса, каждый из которых имеет четыре подслоя. В каждом из 4 подклассов представлений были определены методы touchesBegan:withEvent и touchesEnded:withEvent. Я обнаружил, что если мое прикосновение приземлялось или заканчивалось в самом верхнем левом представлении, мой hitTest возвращал действительный подслой. Однако hitTests в любом из трех других представлений возвращали nil для подуровня. Как и вы, я был в полном тупике, пока не решил заменить точку касания в системе координат вида на точку для системы координат окна, и тогда все заработало. Я воспроизвожу документацию для метода hitTest:

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

- (CALayer *)hitTest:(CGPoint)thePoint

Параметры thePoint Точка в системе координат суперслоя приемника. Возвращаемое значение Слой, содержащий точку, или ноль, если точка находится за пределами прямоугольника границ получателя.

Доступность Доступно в Mac OS X v10.5 и более поздних версиях. Объявлено в CALayer.h

Я бы сказал, основываясь на своих наблюдениях, что объяснение «точки» неверно. Я думаю, это должно читаться как «Точка в системе координат окна, содержащего приемник». Я думаю, что единственная причина, по которой верхний левый вид дал действительные hitTests, заключалась в том, что координаты касания - в этом месте - такие же, как координаты касания в окне. Не знаю, поможет ли это вам, но это помогло мне заставить мою логику работать. В.В.

person VectorVictor    schedule 16.10.2011