Случайное EXC_BAD_ACCESS при добавлении подпредставления с ARC
Мое приложение отлично работает на симуляторе, но когда я тестирую приложение на устройстве, я в итоге получаю случайный EXC_BAD_ACCESS, и приложение вылетает.
После нескольких дней тестирования, я думаю, я нашел код, который вызывает эту ошибку. В какой-то момент мне нужно добавить несколько подпредставлений в основное представление контроллера, затем пользователь взаимодействует с приложением, и эти подпредставления удаляются из их суперпредставления и добавляются новые подпредставления. Если я никогда не удаляю подпредставления, приложение не падает, но если я их удаляю, приложение в конечном итоге получает EXC_BAD_ACCESS и вылетает.
Кажется, что удаленные подпредставления получают сообщение о выпуске, когда они уже полностью выпущены или что-то в этом роде... Это первое приложение, в котором я использую ARC, так что я, вероятно, что-то упускаю...
Вот этот код:
#define kWordXXX 101
#define kWordYYY 102
...
// This is called after the user interaction, it removes the
// old subviews (if they exist) and add new ones
- (void)updateWords
{
[self removeWords];
if (self.game.move.wordXXX) {
WordView *wordXXX = [self wordViewForTypeXXX];
wordXXX.tag = kWordXXX;
// self.wordsView is the view where the subviews are added
[self.wordsView addSubview:wordXXX];
}
if (self.game.move.wordYYY) {
WordView *wordYYY = [self wordViewForTypeYYY];
wordYYY.tag = kWordYYY;
[self.wordsView addSubview:wordYYY];
}
}
// Remove the old words if they exist
- (void)removeWords
{
WordView *wordXXX = (WordView *)[self.wordsView viewWithTag:kWordXXX];
WordView *wordYYY = (WordView *)[self.wordsView viewWithTag:kWordYYY];
if (wordXXX) {
[wordXXX removeFromSuperview];
}
if (wordYYY) {
[wordYYY removeFromSuperview];
}
}
Вот как создаются подпредставления. Я не особенно горжусь этим кодом, и он нуждается в рефакторинге, но мне нужно понять, почему он не работает раньше:
- (WordView *)wordViewWithFrame:(CGRect)frame andType:(WordType)type
{
WordView *wordView = nil;
if (type == SystemWord) {
frame.origin.y += 15;
wordView = [[SystemWordView alloc] initWithFrame:frame];
} else if (type == StartWord) {
wordView = [[StartWordView alloc] initWithFrame:frame];
} else if (type == UserWord) {
wordView = [[UserWordView alloc] initWithFrame:frame];
} else {
wordView = [[RivalWordView alloc] initWithFrame:frame];
}
return wordView;
}
- (WordView *)wordViewForTypeXXX
{
WordType type = self.game.move.wordType;
WordView *wordView = nil;
CGRect wordViewFrame = CGRectMake(0,
0,
self.scoreView.frame.size.width,
35);
wordView = [self wordViewWithFrame:wordViewFrame andType:type];
wordView.word = self.game.move.word;
return wordView;
}
- (WordView *)wordViewForTypeYYY
{
WordType type = self.game.move.wordType;
CGFloat y = self.game.move.word ? 35 : 0;
WordView *wordView = nil;
CGRect wordViewFrame = CGRectMake(0,
y,
self.scoreView.frame.size.width,
35);
wordView = [self wordViewWithFrame:wordViewFrame andType:type];
wordView.word = self.game.move.word;
if (self.game.move.word && [wordView isKindOfClass:[PlayerWordView class]]) {
((PlayerWordView *)wordView).points = [NSNumber numberWithInteger:self.game.move.points];
}
return wordView;
}
Это работает некоторое время, а затем вылетает. Я имею в виду, что представления удаляются и добавляются несколько раз, и кажется, что все в порядке, но через некоторое время приложение получает EXC_BAD_ACCESS.
Любая помощь будет оценена вечно!
PS: простите за мой английский
РЕДАКТИРОВАТЬ: я не могу использовать зомби на устройстве, и я не вижу трассировку стека.
Вот что я получу, если наберу "bt" в lldb после того, как получу EXC_BAD_ACCESS:
* thread #1: tid = 0x2503, 0x3bb735b0 libobjc.A.dylib`objc_msgSend + 16, stop reason = EXC_BAD_ACCESS (code=1, address=0x11d52465)
frame #0: 0x3bb735b0 libobjc.A.dylib`objc_msgSend + 16
frame #1: 0x3473f6fe Foundation`probeGC + 62
frame #2: 0x34745706 Foundation`-[NSConcreteMapTable removeObjectForKey:] + 34
frame #3: 0x360b3d5c UIKit`-[_UIImageViewPretiledImageWrapper dealloc] + 80
frame #4: 0x3bb75488 libobjc.A.dylib`(anonymous namespace)::AutoreleasePoolPage::pop(void*) + 168
frame #5: 0x33e16440 CoreFoundation`_CFAutoreleasePoolPop + 16
frame #6: 0x347ea184 Foundation`__NSThreadPerformPerform + 604
frame #7: 0x33ea8682 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 14
frame #8: 0x33ea7ee8 CoreFoundation`__CFRunLoopDoSources0 + 212
frame #9: 0x33ea6cb6 CoreFoundation`__CFRunLoopRun + 646
frame #10: 0x33e19ebc CoreFoundation`CFRunLoopRunSpecific + 356
frame #11: 0x33e19d48 CoreFoundation`CFRunLoopRunInMode + 104
frame #12: 0x379dd2ea GraphicsServices`GSEventRunModal + 74
frame #13: 0x35d2f300 UIKit`UIApplicationMain + 1120
frame #14: 0x0005bd40 Dr. Cuaicap`main(argc=1, argv=0x2fda8d10) + 116 at main.m:16
frame #15: 0x3bfafb20 libdyld.dylib`start + 4
5 ответов
Обсуждение из комментариев -
Вы правы, зомби будут работать только в симуляторе - хотя я подозреваю, что сбой будет воспроизводиться и в симуляторе. Очень мало случаев, когда приложение зависает на устройстве, но не на симуляторе. Возможно, попробуйте еще раз убедиться, что вы используете симулятор той же версии iOS. Я не смог найти никакой информации о _UIImageViewPretiledImageWrapper, но это будет ключ к решению этой проблемы.
Этот ответ предполагает _UIImageViewPretiledImageWrapper
относится к -[UIImage resizableImageWithCapInsets:]
,
Комментарии от ОП:
В какой-то момент я создавал UIImageView на основе изменяемого размера UIImage, но я устанавливал изображение перед установкой окончательного размера UIImageView. Кажется, это как-то портит память из-за изменения размера UIImage
Просто к вашему сведению.
У меня также был сбой, похожий на этот, и исправление было не в коде, а в самом активе.
Как говорится в документации Apple, область изменения размера актива должна составлять 1 на 1 пиксель, поэтому убедитесь, что это так.
- (UIImage *) resizableImageWithCapInsets: (UIEdgeInsets) capInsets
Во время масштабирования или изменения размера изображения области, покрытые крышкой, не масштабируются и не изменяют размер. Вместо этого область пикселей, не покрытая колпачком в каждом направлении, выложена мозаикой, слева направо и сверху вниз, чтобы изменить размер изображения. Этот метод часто используется для создания кнопок переменной ширины, которые сохраняют те же закругленные углы, но центральная область которых увеличивается или уменьшается по мере необходимости. Для лучшей производительности используйте мозаичную область размером 1x1 пикселя.
Я думаю, как обычно, если приложение работает на симуляторе, а не на устройстве, зависит от памяти. Пожалуйста, проверьте предупреждение памяти.
Это выглядит проблемой с освобождением памяти. объявить
WordView *wordView
в.h файле. Так что это создаст сильную ссылку
Переместите WordView *wordXXX в объявление интерфейса, чтобы сделать его экземпляром переменной.