我正在尝试使用这里出色的代码从iOS相机胶卷中的图像中读取EXIF数据:
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字典 运行。在一些测试中,我已经完成了它有时运行内部的代码 甚至在执行[library autorelease]之前就阻止了。但第一个 你运行它的时间,块内的代码只会在另一个上运行 应用程序主循环的循环。所以,如果你需要正确使用这个信息 离开时,最好在运行队列上安排一个方法以供日后使用 用:
[self performSelectorOnMainThread:SELECTOR withObject:SOME_OBJECT waitUntilDone:NO];
..这就是我坚持的这条线!我不知道该怎么办?
任何帮助都非常感谢!
答案 0 :(得分:3)
在不知道库的情况下,我只能推测加载是异步完成的,因此在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.
});
}
...];
答案 1 :(得分:3)
问题是块中的代码被异步调用(这就是为什么块可能有用,即使这不是它们唯一的典型用法)。
您可能会认为这是另一种方式来完成通常使用委托所做的事情,即在数据可用时异步地被告知。
对于您的代码, ALAssetsLibrary将发出请求以获取和解码您请求的资产网址的EXIF元数据,但代码将继续(在阻止之后) ,没有立即执行的块,因此转到下一行(在您的情况下为[library release]
)。
稍后,一旦ALAssetsLibrary最终检索到您要求的资产信息和ALAsset,它将最终触发您在块中设置的代码 (就像你使用委托时一样,当数据可用时稍后调用委托方法)。
这解释了为什么“块”中的代码将异步执行,然后可以在[library release]
之前或之后执行,并且当您点击[library release]
行(或任何行)时在assetForURL
调用之后放入的代码,块中的代码可能没有时间执行。
解决方案是不使用performSelector
,但更好地放置代码,使得更新界面或处理块本身中的EXIF数据所需的一切。
例如,你可以在这里直接放置代码来更新你的界面(比如[self.tableView reloadData]
如果在tableview中显示EXIF数据),或者激活NSNotification以通知你的其余代码新的已使用addEntriesFromDictionary
添加EXIF数据,需要显示等等)