Код в блоке Objective-C c не выполняется, когда ожидается - PullRequest
3 голосов
/ 21 сентября 2011

Я пытаюсь прочитать в EXIF ​​данные с изображений в рулоне камеры iOS, используя фантастический код здесь:

http://blog.codecropper.com/2011/05/getting-metadata-from-images-on-ios/

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

Автор блога знает об этом и заявляет о решении, но я просто не понимаю его вообще!Я новичок в "блоках" и просто не понимаю, хотя я прочитал: http://thirdcog.eu/pwcblocks/

Кто-нибудь может перевести для меня?

Этокод, используемый для чтения данных:

NSMutableDictionary *imageMetadata = nil;
NSURL *assetURL = [info objectForKey:UIImagePickerControllerReferenceURL];

ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:assetURL
resultBlock:^(ALAsset *asset)  {
    NSDictionary *metadata = asset.defaultRepresentation.metadata;
    imageMetadata = [[NSMutableDictionary alloc] initWithDictionary:metadata];
    [self addEntriesFromDictionary:metadata];
}
failureBlock:^(NSError *error) {
}];
[library autorelease];

, который удобно помещается в метод init и называется следующим образом:

NSMutableDictionary *metadata = [[NSMutableDictionary alloc] initWithInfoFromImagePicker:info];

Описание авторов проблемы первой попытки:

Одно предостережение при использовании этого: поскольку он использует блоки, нет гарантии, что ваш словарь imageMetadata будет заполнен при выполнении этого кода.В некотором тестировании, которое я сделал, иногда запускается код внутри блока даже до того, как будет выполнен [autorelease библиотеки].Но при первом запуске код внутри блока будет выполняться только в другом цикле основного цикла приложений.Так что, если вам нужно использовать эту информацию прямо сейчас, лучше запланировать метод в очереди выполнения на потом:

[self performSelectorOnMainThread:SELECTOR withObject:SOME_OBJECT waitUntilDone:NO];

.. и именно в эту строку я застрял!Я не знаю, что с этим делать?

Любая помощь с благодарностью!

Ответы [ 2 ]

3 голосов
/ 21 сентября 2011

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

Вы можете подуматьэто как еще один способ сделать то, что обычно делается с делегатом, а именно асинхронно сообщается, когда данные доступны.

В случае вашего кода ALAssetsLibrary сделает запрос получить и декодировать метаданные EXIF ​​запрашиваемого URL ресурса, , но код продолжится (после блока, без выполнения блока сразу) , переходя к следующим строкам (в вашемв случае [library release]).

Позже , когда ALAssetsLibrary наконец-то получит информацию об активе и ALAsset, который вы запрашивали, будет, наконец, активировать код, заданный вами вblock (совсем как при использовании делегатов, а методы делегатов вызываются позже, когда доступны данные) .


Это объясняет, почему код в tЭтот «блок» будет выполняться асинхронно, а затем может быть выполнен до или после [library release], и к тому времени, когда вы попадете на строку [library release] (или любой код, который вы вставите после вызова assetForURL), кодв блоке, вероятно, не будет времени для выполнения.

Решение состоит в том, чтобы не использовать performSelector, а лучше поместить код, который делает все необходимое для обновления вашего интерфейса или обработки данных EXIF ​​в самом блоке.

Например, вы можете либо напрямую указать здесь код для обновления вашего интерфейса (например, [self.tableView reloadData], если вы отображаете данные EXIF ​​в виде таблицы), либо запустить NSNotification для информирования остальной части вашего кода о том, чтоновые данные EXIF ​​были добавлены с помощью addEntriesFromDictionary и должны отображаться и т. д.)

3 голосов
/ 21 сентября 2011

Не зная библиотеки, я могу только предположить, что загрузка выполняется асинхронно, и поэтому не гарантируется, что она будет доступна после возврата assetForURL:... (выполняется в фоновом режиме).

Правильное решение этого заключается в том, чтобы поместить в resultBlock весь код, который имеет дело с результатом, а не помещать его после вызова assetForURL:.... Хороший способ убедиться, что вы делаете правильно, - переместить объявление imageMetaData в блок, чтобы не было возможности случайно использовать его за пределами области действия блока (это только внутри область, в которой эта переменная гарантированно будет действительной).

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

[library assetForURL:assetURL
    resultBlock:^(ALAsset *asset)  {
        [self performSelectorOnMainThread:@selector(onAssetRetrieved:)
                               withObject:asset
                            waitUntilDone:NO];
    }
    ...];
...
- (void) onAssetRetrieved:(ALAsset *asset) {
    NSDictionary *metadata = asset.defaultRepresentation.metadata;
    imageMetadata = [[NSMutableDictionary alloc] initWithDictionary:metadata];
    [self addEntriesFromDictionary:metadata];
    // Do whatever you planned to do immediately after the asset was retrieved.
}

РЕДАКТИРОВАТЬ: Я не знал о функциях dispatch_…, когда писал выше. Вместо этого сделайте что-нибудь подобное:

[library assetForURL:assetURL
    resultBlock:^(ALAsset *asset)  {
        dispatch_async(dispatch_get_main_queue(), ^{
            NSDictionary *metadata = asset.defaultRepresentation.metadata;
            imageMetadata = [[NSMutableDictionary alloc] initWithDictionary:metadata];
            [self addEntriesFromDictionary:metadata];
            // Do whatever you planned to do immediately after the asset was retrieved.
        });
    }
    ...];
...