Проблема с ASIHTTPRequest и ошибка EXC_BAD_ACCESS

Я использую массив оболочек ASIHTTPRequest (AsyncImageLoader) для загрузки изображений для ячеек в UITableView.

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

Вот как выглядит моя обертка. self.request имеет свойство сохранения, targetCell - это ячейка, в которую я хочу поместить изображение:

@implementation AsyncImageLoader
- (void)loadImageFromURL:(NSString*)fileName target:(ResultTableViewCell*)targetCell {
    // check for cached images, etc

    NSURL* url = [NSURL URLWithString:fileName];
    [self.request cancel];
    self.request = [ASIHTTPRequest requestWithURL:url];
    self.request.delegate = self;
}

- (void)startLoading {
    [self.request startAsynchronous];
}

- (void)cancel {
    [self.request cancel];
    self.request = nil;
}

- (void)requestFinished:(ASIHTTPRequest*)requestDone {
    // cache image, set cell image...

    self.request = nil;
}

- (void)requestFailed:(ASIHTTPRequest*)requestDone {
    // handle answer as well

    self.request = nil;
}
@end

loadImageFromURL вызывается в cellForRowAtIndexPath для indexPath.row % 6th AsyncImageLoader, поэтому, если я продолжаю прокручивать вверх и вниз, он вызывается снова и снова с одним и тем же объектом, отменяя запросы, поскольку они еще не закончены.

Но я всегда получаю EXC_BAD_ACCESS. Согласно стеку вызовов, это происходит в ASIHTTPRequest markAsFinished, вызывается failWithError, вызывается [self.request cancel] в loadImageFromURL:. Большую часть времени ASIHTTPRequest уже был выпущен (я добавил NSLog в его Dealloc), но я не понимаю, как это возможно, поскольку ASIHTTPRequest, кажется, выдает сохранения, поэтому он не освобождается при отмене.

У меня нет EXC_BAD_ACCESS, если я удаляю self.request = nil в методах делегата, но поскольку ASIHTTPRequests продолжают создаваться без освобождения, они в конечном итоге вообще не работают.

Может ли кто-нибудь сказать мне, что я делаю неправильно?


person Jukurrpa    schedule 20.07.2010    source источник
comment
Вы вызываете отмену для нулевого объекта, вот что вы делаете неправильно. Как вы сами выяснили, установка запроса на nil в методах делегата не очень хорошая идея: правильное место для этого - (void)viewDidUnload   -  person Elise van Looij    schedule 29.12.2011


Ответы (4)


Когда вы сталкиваетесь с проблемой, когда вы получаете EXC_BAD_ACCESS, это означает, что вы что-то перевыпустили. В большинстве случаев вам просто нужно запустить отладчик (Command-Y) и посмотреть последнюю строку вашего кода в трассировке стека при сбое, чтобы узнать, какой объект перевыпущен. Если это не сработает, вам нужно включить зомби и посмотреть, какой объект доступ снова после того, как он был освобожден. Это почти всегда укажет вам правильное направление.

В качестве примечания, есть несколько других библиотек, которые делают то, что вы пытаетесь сделать. Я не использовал его много, но похоже, что он по крайней мере имеет потенциал. Проверьте: SDWebImage. Может быть, это натолкнет вас на некоторые идеи.

С наилучшими пожеланиями.

person Matt Long    schedule 20.07.2010
comment
Спасибо за Ваш ответ. Я активировал Zombie, и он указал, что проблема вызвана сохранением, вызванным освобожденным экземпляром ASIHTTPRequest. Я не понимаю, почему это происходит, так как в AsyncImageLoader выполняется только одно сохранение и освобождение, а ASIHTTPRequest должен сохранять себя достаточно, чтобы работать правильно. Причина может заключаться в том, что когда я вызываю [self.request cancel] в loadImageFromURL, self.request уже выпущен, но для него должно быть установлено значение nil, поскольку методы requestFinished/Failed были вызваны в основном потоке. - person Jukurrpa; 20.07.2010
comment
Трудно сказать наверняка, но я думаю, вам, вероятно, нужно сделать дополнительный рефакторинг. Вот что я имею в виду. Что произойдет, если вы прокрутите представление таблицы, пока текущий запрос все еще находится на рассмотрении? Вы прошли в ячейку табличного представления, я предполагаю, что вы собираетесь заполнить изображением после того, как оно завершит загрузку, но если пользователь сейчас прокрутил, изображение, которое вы хотите показать, теперь будет неправильным. Было бы лучше передать делегата, который вы можете уведомить, когда изображение будет доступно. Затем этот делегат может определить, должен ли он по-прежнему заполнять текущую ячейку этим изображением. - person Matt Long; 20.07.2010
comment
Другими словами, я хочу сказать, что вам нужно немного пересмотреть свой дизайн, который вполне может решить эту проблему намеренно или нет. - person Matt Long; 20.07.2010
comment
Ну, я думал о разных концепциях, но в конечном итоге мне всегда придется отменять загрузку изображения, чтобы не запускать десятки запросов, которые будут полностью выполняться «впустую» и будут замедлять другие загрузки, или даже приложение сам. Я попробую немного изменить дизайн, но боюсь, что проблема останется. - person Jukurrpa; 20.07.2010

Это очень похоже на аварию, которую я вижу.

Я еще не уверен, в чем причина, но я твердо уверен, что это не связано с клиентским кодом (то есть либо в ASIHTTPRequest, либо в ОС).

Я исправил некоторые проблемы с запросом asi http, но этот сбой остался.

Я пытался получить помощь на форумах Apple здесь:

https://devforums.apple.com/thread/59843?tstart=0

Вот как выглядит крах у меня:

#2  0x32c02e14 in CFRetain ()
#3  0x32c709b6 in __CFTypeCollectionRetain ()
#4  0x32c06c60 in _CFArrayReplaceValues ()
#5  0x32b0994c in -[NSCFArray insertObject:atIndex:] ()
#6  0x32b098f0 in -[NSCFArray addObject:] ()
#7  0x32b709f0 in __chooseAll ()
#8  0x32b71bde in __finishedOp ()
#9  0x32b26636 in +[NSOperation observeValueForKeyPath:ofObject:change:context:] ()
#10 0x32b265aa in NSKVONotify ()
#11 0x32b13306 in -[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] ()
#12 0x0007d6ec in -[ASIHTTPRequest markAsFinished] (self=0x5a7cc40, _cmd=0xa1710) at ASIHTTPRequest.m:2817
#13 0x00077e02 in -[ASIHTTPRequest failWithError:] (self=0x5a7cc40, _cmd=0x330cdfc4, theError=0x248130) at ASIHTTPRequest.m:1708
#14 0x000712dc in -[ASIHTTPRequest cancel] (self=0x5a7cc40, _cmd=0x322b4298) at ASIHTTPRequest.m:515
#15 0x0008884c in -[UIHTTPImageView setImageWithURL:placeholderImage:] (self=0x5a7c9d0, _cmd=0xa3f0c, url=0x5aa7760, placeholder=0x0) at UIHTTPImageView.m:21
#16 0x0006183c in -[MeetingView tiledScrollView:tileForRow:column:resolution:] (self=0x5a32560, _cmd=0x9bfac, tiledScrollView=0x5a74570, row=1, column=0, resolution=0) at MeetingView.m:1053
#17 0x0000475e in -[TiledScrollView layoutSubviews] (self=0x5a74570, _cmd=0x32299680) at TiledScrollView.m:181
#18 0x31515f32 in -[UIView(CALayerDelegate) _layoutSublayersOfLayer:] ()
#19 0x32c29ffa in -[NSObject performSelector:withObject:] ()
#20 0x30964798 in -[CALayer layoutSublayers] ()
#21 0x30964568 in CALayerLayoutIfNeeded ()
#22 0x30959b62 in CA::Context::commit_transaction ()
#23 0x3095997a in CA::Transaction::commit ()
#24 0x3097f164 in CA::Transaction::observer_callback ()
#25 0x32c702a0 in __CFRunLoopDoObservers ()
#26 0x32c23bb0 in CFRunLoopRunSpecific ()
#27 0x32c234e0 in CFRunLoopRunInMode ()
#28 0x30d620da in GSEventRunModal ()
#29 0x30d62186 in GSEventRun ()
#30 0x314d54c8 in -[UIApplication _run] ()
#31 0x314d39f2 in UIApplicationMain ()
#32 0x0000245c in main (argc=1, argv=0x2ffff5b4) at main.m:14

Насколько я могу судить, действия выглядят примерно так:

  1. Запрос создан и запущен и, следовательно, добавлен в очередь
  2. Запрос отменяется до того, как он начнет выполняться
  3. Запрос успешно отменен, удален из очереди и освобожден
  4. Другой запрос завершается, и очередь вызовов сохраняется в освобожденном запросе.

В качестве эксперимента я попытался сохранить запросы еще раз - это остановило сбой, но очередь перестала работать.

Возможно, ASIHTTPRequest все еще каким-то образом неправильно обрабатывает запрос на отмену, но мне трудно понять, как это сделать.

Обновить

Это должно исправить это:

http://github.com/jogu/asi-http-request/commit/887fcad0f77e9717f003273612804a9b9012a140

Насколько я могу судить, NSOperationQueue перешел в плохое состояние после того, как ему сказали, что незапущенный запрос завершился.

person JosephH    schedule 21.07.2010
comment
Привет, я также уверен, что эта ошибка исходит от ASIHTTPRequest (или, может быть, iOS). Цепочка действий, которую вы перечислили, точно такая же в моей ситуации, поэтому я считаю, что отмена запроса может быть нестабильной. Как посоветовал Мэтт Лонг, я скорее искал другой дизайн (см. Мой ответ). - person Jukurrpa; 21.07.2010
comment
Я думаю, что это ASIHTTPRequest, я только что обновил свой ответ ссылкой на исправление, если вы хотите попробовать его. - person JosephH; 22.07.2010

Отвечаю самому себе как синтез моего заключения и других ответов.

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

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

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

person Jukurrpa    schedule 21.07.2010

попробуйте добавить что-то вроде этого в Dealloc вашего представления:

    [self.thumbnailRequest cancel];
self.thumbnailRequest.delegate = nil;
[self.thumbnailRequest setDidFinishSelector:NULL];
[self.thumbnailRequest setDidFailSelector:NULL];
self.thumbnailRequest = nil;
person scurioni    schedule 15.10.2012