проблема с делегатом объекта, который является делегатом NSURLConnection

У меня есть класс PictureDownloader для асинхронной загрузки изображений с сервера. Он назначает себя делегатом NSURLConnection и, как таковой, сохраняется NSURLConnection. Я создаю несколько таких PictureDownloader в DetailViewController для получения соответствующих изображений, поэтому DetailViewController является делегатом каждого PictureDownloader. Когда пользователь покидает DetailViewController, все оставшиеся загрузки отменяются, однако иногда кажется, что PictureDownloader завершил загрузку изображения (вызывается connectionDidFinishedLoading) до того, как соединение было отменено, но DetailViewController больше не существует (но PictureDownloader делает, потому что он сохраняется NSURLConnection), поэтому вызов

[self.delegate didLoadPictureWithID:self.ID];

внутри PictureDownloader выдаст EXC_BAD_ACCESS или иногда «неопознанный селектор, отправленный экземпляру».

Вот соответствующие части исходного кода:

создание PictureDownloader внутри DetailViewController

- (void)startPictureDownload:(Picture *)pic withPictureId:(NSString *)pId forID:(int)ID
{
    PictureDownloader *downloader = [self.downloadsInProgress objectForKey:[NSNumber numberWithInt:ID]];
    if(!downloader)
    {
        downloader = [[PictureDownloader alloc] init];
        downloader.picture = pic;
        downloader.pictureId = pId;
        downloader.ID = ID;
        downloader.delegate = self;
        [self.downloadsInProgress setObject:downloader forKey:[NSNumber numberWithInt:ID]];
        [downloader startDownload];
        [downloader release];
    }
}

отмена загрузок (вызывается, когда DetailViewController возвращается к обзору)

- (void)cancelAllDownloads
{
    [self.downloadsInProgress enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){
        [obj cancelDownload];
    }];
}

метод делегата, который вызывается после завершения загрузки PictureDownloader

- (void)didLoadPictureWithID:(int)dID;
{
    PictureDownloader *downloader = [self.downloadsInProgress objectForKey:[NSNumber numberWithInt:dID]];

    if(downloader)
    {
        UIImageView *imageView = (UIImageView *)[self.view viewWithTag:dID];
        imageView.image = [UIImage imageWithData:downloader.imageData];

        [self.downloadsInProgress removeObjectForKey:[NSNumber numberWithInt:dID]];
    }
}

cancelDownload внутри PictureDownloader

- (void)cancelDownload
{
    [self.imageConnection cancel];
    self.imageConnection = nil;
    self.imageData = nil;
}

connectionDidFinishedЗагрузка внутри PictureDownloader

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    if(self.picture)
    {
        self.picture.data = self.imageData;
        NSError *error = nil;
        [self.picture.managedObjectContext save:&error];
    }

    if(self.delegate != nil && [self.delegate respondsToSelector:@selector(didLoadPictureWithID:)] ) //place of failure
        [self.delegate didLoadPictureWithID:self.ID];

    self.imageData = nil;
    self.imageConnection = nil;
}

Может кто-нибудь подскажет, как я могу решить эту проблему?

Помощь очень ценится.


person Sam Hitz    schedule 07.03.2011    source источник


Ответы (3)


Чтобы избежать подобных ситуаций, я обычно добавляю такую ​​проверку вверху connectionDidFinishLoading: и других методов делегата NSURLConnection:

if (connection != self.imageConnection) return;

В качестве другого варианта вы можете установить для делегата каждого PictureDownloader значение nil при его отмене в cancelAllDownloads. Или вы можете установить self.delegate = nil в cancelDownload.

person Anomie    schedule 07.03.2011

Вы должны проверить наличие объекта делегата (и, в идеале, метода / селектора), прежде чем пытаться сделать вызов.

Например:

if(self.delegate && [[self.delegate] respondsToSelector:@selector(didLoadPictureWithID:)]) {
    ...
}

Таким образом вы убедитесь, что не пытаетесь вызвать делегата, которого больше нет. Для получения дополнительной информации о методе responsedsToSelector см. Справочник по протоколу NSObject.

person John Parker    schedule 07.03.2011
comment
Спасибо за быстрый ответ. Похоже, это не решает проблему полностью. Если self.delegate указывает на область в памяти, которая недоступна, то вызов RespondsToSelector вызовет исключение EXC_BAD_EXCESS. Но, по крайней мере, похоже, что нераспознанный селектор, отправленный на сбой экземпляра, исчез. - person Sam Hitz; 07.03.2011
comment
@Sambo Вам может нужно использовать self.delegate != nil в качестве первого предложения. (На данный момент у меня нет доступа к Mac OS X, иначе я бы проверил.) - person John Parker; 07.03.2011
comment
Похоже, это не имеет значения. - person Sam Hitz; 07.03.2011
comment
@Sambo Не могли бы вы обновить свой вопрос с помощью примера кода, показывающего деятельность, связанную с делегатом. (то есть: как класс, который использует класс делегата, регистрирует / отменяет регистрацию самого себя.) - person John Parker; 07.03.2011
comment
надеюсь, это поможет, иначе просто попросите больше;) - person Sam Hitz; 07.03.2011

Когда ваш DetailViewController выходит за пределы области видимости - dealloc -, также установите для свойства делегата PictureDownloader значение nil.

Ваша проблема интересна тем, что делегат NSUrlConnection не может быть таким же образом установлен в ноль. например Когда вы используете PictureDownloader, доступ к нему отключен. Все, что вы можете сделать, это отменить NSUrlConnection.

В документации NSURLConnection говорится следующее:

Если NSURLConnection не получит сообщение об отмене, делегат получит одно и только одно сообщение из connectionDidFinishLoading: или connection: didFailWithError:, но не оба сразу. Кроме того, после отправки любого из сообщений делегат больше не будет получать сообщения для данного NSURLConnection.

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

person paiego    schedule 17.11.2011