Objective-C c中的代码在预期时未执行

时间:2011-09-20 23:20:55

标签: objective-c c ios4 objective-c-blocks

我正在尝试使用这里出色的代码从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];

..这就是我坚持的这条线!我不知道该怎么办?

任何帮助都非常感谢!

2 个答案:

答案 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数据,需要显示等等)