I'm trying to read in EXIF data from images in the iOS camera roll using the fantastic code here:
http://blog.codecropper.com/2011/05/getting-metadata-from-images-on-ios/
Unfortunately, on the first attempt to read the data, nil is returned ... but every subsequent attempt works fine.
The author of the blog is aware of this and states a solution, but I just don't understand it at all! I'm new to "blocks" and I'm just not getting it even though I've read: http://thirdcog.eu/pwcblocks/
Can anyone translate for me?
This is the code being used to read in the data:
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];
which is conveniently put into an init method and called like:
NSMutableDictionary *metadata = [[NSMutableDictionary alloc] initWithInfoFromImagePicker:info];
The authors description of the first attempt problem is:
One caveat on using this: because it uses blocks, there’s no guarantee that your imageMetadata dictionary will be populated when this code runs. In some testing I’ve done it sometimes runs the code inside the block even before the [library autorelease] is executed. But the first time you run this, the code inside the block will only run on another cycle of the apps main loop. So, if you need to use this info right away, it’s better to schedule a method on the run queue for later with:
[self performSelectorOnMainThread:SELECTOR withObject:SOME_OBJECT waitUntilDone:NO];
.. and it's this line I'm stuck at! I don't know what to do with it?
Any help greatly appreciated!
The thing is that the code in the block is called asynchrnously (that's why blocks can be useful for, even if this is not their only typical usage).
You may think of it as another way to do what is usually done with delegate, namely asynchronouly being told when the data is available.
In the case of your code, the ALAssetsLibrary will make a request to get and decode the EXIF metadata of the asset URL you are requesting, but the code will continue (after the block, w/o the block being executed right away), thus going to the next lines (in your case the [library release]).
Later, once the ALAssetsLibrary finally have retrieved the asset info and the ALAsset your were requesting, it will finally trigger the code you set in the block (quite like when you use delegates and the delegate methods are called later when the data is available).
This explains why the code in the "block" will be executed asynchrnously, and may then be executed before or after the [library release], and by the time you will hit the [library release] line (or any code you put after the assetForURL call), the code in the block will probably not had time to execute.
The solution is no to use performSelector but better put the code that makes everything needed to update your interface or to process the EXIF data in the block itself.
For example, you may either directly put code to update your interface here (like [self.tableView reloadData] if you display EXIF data in a tableview), or fire an NSNotification to inform the rest of your code that new EXIF data has been added using addEntriesFromDictionary and needs to be displayed, etc)
Without knowing the library, I can only speculate that the loading is done asynchronously, and is thus not guaranteed to be available after assetForURL:... returns (it's being performed in the background).
The correct solution to this is to place into resultBlock all the code that deals with the result instead of placing it after the call to assetForURL:.... A good way to ensure that you are doing the right thing is to move the declaration of imageMetaData into the block, so that there's no chance of accidentally using it outside the block's scope (it is only inside the scope that this variable is guaranteed to be valid).
I get the impression that the author doesn't quite grok what's going on and is suggesting that by using performSelectorOnMainThread:..., you will give assetForURL:... a chance to complete. Again, without knowing the library, I can only speculate, but this sounds like it might be just a way to reduce the likelihood of reading the variable prematurely, without actually solving the problem. Having said that, calling performSelectorOnMainThread:... from inside the block is a good way to ensure that the processing of the result continues on the main thread, just in case resultBlock is called from another thread within the library. It might look something like this:
[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: I didn't know about the dispatch_… functions when I wrote the above. Do something like this instead:
[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.
});
}
...];
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With