Возврат значения, полученного в отдельном потоке с использованием GCD - PullRequest
1 голос
/ 14 января 2012

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

- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
    [(UIImageView *)view.leftCalloutAccessoryView setImage:[self.delegate mapViewController:self imageForAnnotation:view.annotation]];
}

Класс делегата получает изображение из сети.Чтобы защитить пользовательский интерфейс от отсутствия ответа, создается новый поток для загрузки изображения с использованием GCD.

- (UIImage *)mapViewController:(MapViewController *)sender imageForAnnotation:(id<MKAnnotation>)annotation
{
    NSURL *someURL = [[NSURL alloc] initWithString:@"a URL to data on a network"];
    __block UIImage *image = [[UIImage alloc] init];

    dispatch_queue_t downloader = dispatch_queue_create("image downloader", NULL);
    dispatch_async(downloader, ^{
        NSData *imageData = [NSData dataWithContentsOfURL:someURL];  // This call can block the main UI!
        image = [UIImage imageWithData:imageData];
    });

    return image;
}

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

Ответы [ 2 ]

3 голосов
/ 14 января 2012

Вы создаете блок, который выполняется асинхронно. Это означает, что ваш код создал блок для выполнения, а затем сразу же возвратил «изображение», которое указывало на новое изображение, которое вы создали при инициализации переменной: __block UIImage *image = [[UIImage alloc] init]; Помните, что когда вы возвращаете объект из метода, вы на самом деле просто возвращаете указатель на этот объект.

Через некоторое время после возвращения указателя на это новое изображение. Блок запускается и назначает указатель на изображение, которое он получил, на локальную переменную 'image', которая теперь выходит за рамки метода (хотя блок все еще имеет ее). Так что теперь у блока есть эта ссылка на полученное изображение, но эта ссылка исчезнет, ​​когда блок завершится.

Одним из способов решения этой проблемы является синхронный запуск блока, но это будет препятствовать отправке процесса поиска изображения. Что вам нужно сделать, так это предоставить блок функции, которую он может вызвать после получения изображения, а именно назначить изображение там, где оно должно быть. Это будет выглядеть примерно так:

- (void)mapViewController:(MapViewController *)sender imageForAnnotation:(id<MKAnnotation>)annotation withImageBlock:(void (^)(UIImage *))block{
{
    NSURL *someURL = [[NSURL alloc] initWithString:@"a URL to data on a network"];
    __block UIImage *image = [[UIImage alloc] init];

    dispatch__object_t currentContext = dispatch_get_current_queue();
    dispatch_queue_t downloader = dispatch_queue_create("image downloader", NULL);
    dispatch_async(downloader, ^{
        NSData *imageData = [NSData dataWithContentsOfURL:someURL];  // This call can block the main UI!
        image = [UIImage imageWithData:imageData];
        dispatch_async(currentContext, ^{
            block(image);
        });
    });
}

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

1 голос
/ 14 января 2012

К сожалению, то, что вы пытаетесь сделать здесь, совсем не просто.Вам нужно создать «будущее» (http://en.wikipedia.org/wiki/Futures_and_promises) и вернуть то, что ObjC / Cocoa не имеет встроенной реализации.

Лучшее, что вы можете здесь сделать, это либо a) иметьвызывающий передает блок, который запускается после завершения загрузки, и обновляет пользовательский интерфейс, или b) возвращает заполнитель изображения и планирует блок для замены изображения после его завершения загрузки.Оба из них потребуют некоторой перестройки вашего кода.Последний также требует, чтобы ваш загружающий код знал, как обновить ваш пользовательский интерфейс, что немного печально с точки зрения увеличения связи.

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