Код в блоке Objective-C c не выполняется, как ожидалось

Я пытаюсь прочитать данные 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];

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

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

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

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

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

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

Любая помощь очень ценится!


person Steven Elliott    schedule 20.09.2011    source источник


Ответы (2)


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

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

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

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


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

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

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

person AliSoftware    schedule 20.09.2011
comment
Спасибо за объяснение. Я выбрал этот ответ, потому что использовал NSNotification в своем решении. Спасибо. - person Steven Elliott; 21.09.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.
}

EDIT: я не знал о функциях 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.
        });
    }
    ...];
person Marcelo Cantos    schedule 20.09.2011
comment
Спасибо за ваш ответ. Я пытался сделать такой же код (blog.codecropper.com/2011/05/) более синхронным без особого успеха. Я либо застреваю в мертвой блокировке, либо получаю исключения из-за того, что блок выполняется через несколько секунд. У меня было искушение попробовать переписать его, чтобы не использовать блоки и получить более асинхронное выполнение, но, похоже, это встроено в фактический вызов API. - person Sebastian Dwornik; 15.08.2012
comment
@SebastianDwornik: Нет проблем. Я должен отметить, что мой ответ был излишне сложным, так как я не знал о функциях dispatch_…. Вместо того, чтобы помещать нижестоящий код в отдельный метод, вызываемый через PerformSelector…, вы можете просто обернуть код в dispatch_async(dispatch_get_main_queue(), ^{ … }). - person Marcelo Cantos; 15.08.2012
comment
Извините за мою нубскую неопытность во взломе блоков GCD+. Не могли бы вы уточнить альтернативу кода dispatch_async с обновлением текста вашего ответа? Когда я попробовал этот метод каким-то образом, и он не совсем сработал для меня. :/ - person Sebastian Dwornik; 15.08.2012