High memory usage looping through PHAssets and calling requestImageForAsset - ios

I'm using an image picker library to allow the user to select many images from their photo library. They are returned as an array of PHAssets. Then, I want to convert all the PHAssets to UIImages and write them to the app's storage.
At the moment, I'm looping through all the assets and calling requestImageForAsset synchronously. My issue is that there is incredibly high memory usage spike when this loop is being run (with 30 images, it spikes up to 130MB). I would like to prevent this.
Here is my code:
for(PHAsset *asset in self.assets) {
NSLog(#"started requesting image %i", i);
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFit options:[self imageRequestOptions] resultHandler:^(UIImage *image, NSDictionary *info) {
dispatch_async(dispatch_get_main_queue(), ^{
assetCount++;
NSError *error = [info objectForKey:PHImageErrorKey];
if (error) NSLog(#"Image request error: %#",error);
else {
NSString *imagePath = [appDelegate.docsPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%i.png",i]];
NSData *imageData = UIImagePNGRepresentation(image);
if(imageData) {
[imageData writeToFile:imagePath atomically:YES];
[self.imagesArray addObject:imagePath];
}
else {
NSLog(#"Couldn't write image data to file.");
}
[self checkAddComplete];
NSLog(#"finished requesting image %i", i);
}
});
}];
i++;
}
Based on the logs, I see that all of the "starting requesting image x" are called first, then all of the completion blocks ("finished requesting image x"). I think that this might be contributing to the memory issue. It would probably be less memory intensive to ensure that the completion block for each iteration is called before freeing those resources and moving to the next iteration. How can I do this?

#Inder Kumar Rathore trick does not work for me.
So i tried read more about PHImageManager here
I found that if i switch from
- requestImageForAsset:targetSize:contentMode:options:resultHandler:
to
- requestImageDataForAsset:options:resultHandler:
i will receive the image with the same dimension {5376, 2688} but the size in byte is much smaller. So the memory issue is solved.
hope this help !!
(note : [UIImage imageWithData:imageData] use this to convert NSData to UIImage)

Please use autoreleasepool for memory management.
for(PHAsset *asset in self.assets) {
// This autorelease pool seems good (a1)
#autoreleasepool {
NSLog(#"started requesting image %i", i);
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:PHImageManagerMaximumSize contentMode:PHImageContentModeAspectFit options:[self imageRequestOptions] resultHandler:^(UIImage *image, NSDictionary *info) {
dispatch_async(dispatch_get_main_queue(), ^{
//you can add autorelease pool here as well (a2)
#autoreleasepool {
assetCount++;
NSError *error = [info objectForKey:PHImageErrorKey];
if (error) NSLog(#"Image request error: %#",error);
else {
NSString *imagePath = [appDelegate.docsPath stringByAppendingPathComponent:[NSString stringWithFormat:#"%i.png",i]];
NSData *imageData = UIImagePNGRepresentation(image);
if(imageData) {
[imageData writeToFile:imagePath atomically:YES];
[self.imagesArray addObject:imagePath];
}
else {
NSLog(#"Couldn't write image data to file.");
}
[self checkAddComplete];
NSLog(#"finished requesting image %i", i);
}
} //a2 ends here
});
}];
i++;
} // a1 ends here
}

I solved it with a recursive method that ensures that the completion block is finished before next iteration. This way it is possible to get thousands of photos with very low memory.
Here is what it does:
Given an array of indices of selected photos in the camera roll
Request first object in array.
In completion block, remove first object in array and call same method again.
Repeat until array is empty
Here is the code.
- (void)processPhotos
{
NSIndexPath *indexPath = [_selectedPhotosArray objectAtIndex:0];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
PHAsset *asset = [_allPhotos objectAtIndex:indexPath.row];
[self.imageManager requestImageForAsset:asset
targetSize:PHImageManagerMaximumSize
contentMode:PHImageContentModeAspectFill
options:self.imageRequestOptions
resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
NSError *error = [info objectForKey:PHImageErrorKey];
if (_selectedPhotosArray.count > 0) {
[_selectedPhotosArray removeObjectAtIndex:0];
}
if (error) {
NSLog(#"[CameraRoll] Image request error: %#",error);
} else {
if (result != nil) {
[self processImage:result];
}
}
if (_selectedPhotosArray.count > 0) {
// Recurring loop
[self processPhotos];
}
}];
});
}
Make sure to check if array not empty before calling this method for the first time.

Related

Memory issue while fetching images from Photos library with metadata

I'm trying to get all photos from photos library with image's metadata. It works fine for 10-20 images but when there are 50+ images it occupies too much memory, which causes to app crash.
Why i need all images into array?
Answer - to send images to server app. [i'm using GCDAsyncSocket to send data on receiver socket/port and i don't have that much waiting time to request images from PHAsset while sending images on socket/port.
My Code :
+(void)getPhotosDataFromCamera:(void(^)(NSMutableArray *arrImageData))completionHandler
{
[PhotosManager checkPhotosPermission:^(bool granted)
{
if (granted)
{
NSMutableArray *arrImageData = [NSMutableArray new];
NSArray *arrImages=[[NSArray alloc] init];
PHFetchResult *result = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeImage options:nil];
NSLog(#"%d",(int)result.count);
arrImages = [result copy];
//--- If no images.
if (arrImages.count <= 0)
{
completionHandler(nil);
return ;
}
__block int index = 1;
__block BOOL isDone = false;
for (PHAsset *asset in arrImages)
{
[PhotosManager requestMetadata:asset withCompletionBlock:^(UIImage *image, NSDictionary *metadata)
{
#autoreleasepool
{
NSData *imageData = metadata?[PhotosManager addExif:image metaData:metadata]:UIImageJPEGRepresentation(image, 1.0f);
if (imageData != nil)
{
[arrImageData addObject:imageData];
NSLog(#"Adding images :%i",index);
//--- Done adding all images.
if (index == arrImages.count)
{
isDone = true;
NSLog(#"Done adding all images with info!!");
completionHandler(arrImageData);
}
index++;
}
}
}];
}
}
else
{
completionHandler(nil);
}
}];
}
typedef void (^PHAssetMetadataBlock)(UIImage *image,NSDictionary *metadata);
+(void)requestMetadata:(PHAsset *)asset withCompletionBlock:(PHAssetMetadataBlock)completionBlock
{
PHContentEditingInputRequestOptions *editOptions = [[PHContentEditingInputRequestOptions alloc]init];
editOptions.networkAccessAllowed = YES;
[asset requestContentEditingInputWithOptions:editOptions completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info)
{
CIImage *CGimage = [CIImage imageWithContentsOfURL:contentEditingInput.fullSizeImageURL];
UIImage *image = contentEditingInput.displaySizeImage;
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(image,CGimage.properties);
});
CGimage = nil;
image = nil;
}];
editOptions = nil;
asset =nil;
}
+ (NSData *)addExif:(UIImage*)toImage metaData:(NSDictionary *)container
{
NSData *imageData = UIImageJPEGRepresentation(toImage, 1.0f);
// create an imagesourceref
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);
// this is the type of image (e.g., public.jpeg)
CFStringRef UTI = CGImageSourceGetType(source);
// create a new data object and write the new image into it
NSMutableData *dest_data = [[NSMutableData alloc] initWithLength:imageData.length+2000];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)dest_data, UTI, 1, NULL);
if (!destination) {
NSLog(#"Error: Could not create image destination");
}
// add the image contained in the image source to the destination, overidding the old metadata with our modified metadata
CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef) container);
BOOL success = NO;
success = CGImageDestinationFinalize(destination);
if (!success) {
NSLog(#"Error: Could not create data from image destination");
}
CFRelease(destination);
CFRelease(source);
imageData = nil;
source = nil;
destination = nil;
return dest_data;
}
Well it's not a surprise that you arrive into this situation, since each of your image consumes memory and you instantiate and keep them in memory. This is not really a correct design approach.
In the end it depends on what you want to do with those images.
What I would suggest is that you keep just the array of your PHAsset objects and request the image only on demand.
Like if you want to represent those images into a tableView/collectionView, perform the call to
[PhotosManager requestMetadata:asset withCompletionBlock:^(UIImage *image, NSDictionary *metadata)
directly in the particular method. This way you won't drain the device memory.
There simply is not enough memory on the phone to load all of the images into the photo library into memory at the same time.
If you want to display the images, then only fetch the images that you need for immediate display. For the rest keep just he PHAsset. Make sure to discard the images when you don't need them any more.
If you need thumbnails, then fetch only the thumbnails that you need.
If want to do something with all of the images - like add a watermark to them or process them in some way - then process each image one at a time in a queue.
I cannot advise further as your question doesn't state why you need all of the images.

CIImage with CGImage memory grow up

I'm creating a photo slideshow app.
My app flow :
user select photo assets > (over 100+)
load images from assets > (display image size)
set imageView image or add CIFilter to image and slides
Question :
When I create CIImage Object from CGImage , the memory is growing up so fast and if the image count over 100+, the app crash.
But it's strange that I remove the create code, the app works fine.
Can anybody help me ?
more code :
- (void)loadDisplayImagesWithCompletion:(void (^)(NSArray *images))completion {
dispatch_async(dispatch_queue_create("PhotosEditViewController_loadImageQueue", nil), ^{
__block NSMutableArray *images = [NSMutableArray array];
__block int handleCount = 0;
__weak PhotosEditViewController *weakSelf = self;
for (PHAsset *asset in self.photoAssets) {
[KPPhotoManager requestImageForAsset:asset targetSize:self.contentView.frame.size completeBlock:^(UIImage *image) {
[images addObject:image];
#autoreleasepool {
CIImage *ciImage = [CIImage imageWithCGImage:[image CGImage]];//memory growing up
}
}];
}
});
}
// KPPhotoManager
+ (void)requestImageForAsset:(PHAsset*)asset targetSize:(CGSize)size completeBlock:(void (^)(UIImage *image))completeBlock
{
[[PHImageManager defaultManager] requestImageForAsset:asset targetSize:size contentMode:PHImageContentModeAspectFill options:[self createImageRequestOptions] resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
completeBlock(result);
}];
}
I created a new project copying your code. I increased the number of calls to 10000 and inspecting the memory all looks well. It does jump a bit but does not inflate.
I also tried to force image load using:
for (int i=0; i<10000; i++) {
#autoreleasepool {
UIImage *image = [UIImage imageWithContentsOfFile:[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"image2.png"]];
[self createFilterImageWithImage:image];
}
}
and it still does not inflate the memory.
How are you inspecting your memory leak? Are you looking at the memory consumption? Are there any specifics in your project or file such as having disabled ARC?

Generating video thumbnails of all videos from gallery gives memory issues

I am working on an application where I need to show the thumbnails of all the videos in my gallery(viewed as collection view).Now I am using AVAssetImageGenerator to generate the thumbnails from videos in gallery but I am getting memory issues.Here is my code that I am using:
PHFetchResult *fetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeVideo options:nil];
PHImageManager *imageManager = [PHImageManager new];
for(NSInteger i=0 ; i < fetchResult.count ; i++){
__weak SAVideosViewController *weakSelf = self;
[imageManager requestAVAssetForVideo:fetchResult[i] options:nil resultHandler:^(AVAsset * _Nullable asset, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
[collectionViewData addObject:asset];
//my method to generate video thumbnail...
[self generateThumbnailForAsset:asset];
if(i == fetchResult.count-1){
collectionViewDataFilled = YES;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.myCollectionView reloadData];
});
}
}];
}
Here is the method called above:
-(void)generateThumbnailForAsset:(AVAsset*)asset_{
AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:asset_];
CMTime time = CMTimeMakeWithSeconds(1,1);
CMTimeShow(time);
CGImageRef img = [imageGenerator copyCGImageAtTime:time actualTime:NULL error:NULL];
if(img != nil){
NSLog(#"image");
[thumbnails addObject:[UIImage imageWithCGImage:img]];
}
CGImageRelease(img);
}
I want to know why I'm getting memory issues here and how can I resolve it.
Seems like no one knows the correct answer . So here I am answering my own question after some research .
NOTE : To do such a thing,you do not need to use AVAssetImageGenerator.You the following methods instead(PHOTOS FRAMEWORK).
PHCachingImageManager *cachingImageManager;
[cachingImageManager startCachingImagesForAssets:collectionViewData targetSize:cellSize contentMode:PHImageContentModeAspectFill options:nil];
The class used here is PHCachingImageManager.Read about this in apple docs.
Then after this,use this second method to retrieve data from cache.
[cachingImageManager requestImageForAsset:collectionViewData[indexPath.row] targetSize:AssetGridThumbnailSize contentMode:PHImageContentModeAspectFill options:nil resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
cell.myImageView.image = result;
}];
The callback handler gives an image that you can use in your collection View cells.use this in cellForItemAtIndexPath method.
For a complete code and reference,refer to this example by APPLE.
https://developer.apple.com/library/ios/samplecode/UsingPhotosFramework/Introduction/Intro.html

Memory issue generated in iPad because data receiving very lengthy

I am struck in a big problem. What I am trying to do is getting data from the server but the server in a single hit give all the data which is very large in quantity and I am doing all my process on the main thread, So there are around 400-500 images in the form of URL which I am saving in document directory in the form of NSData. So in the dubug navigator when the memory consumption reached around 80-90 mb then my application crashed and showing the following error:-
mach_vm_map(size=135168) failed (error code=3)
*** error: can't allocate region
*** set a breakpoint in malloc_error_break to debug
2015-01-23 17:10:03.946 ArchaioMobileViewer[853:148470] *** Terminating app due to uncaught exception 'NSMallocException', reason: 'Attempt to allocate 262144 bytes for NS/CFData failed'
I am Using ARC but still I am getting the memory problem. This is my code `-
(void)downloadDocumentsFromServer:(NSDictionary *)documentsList IsUpdate:(BOOL)isUpdate;
{
//Main Target(22)
BusinessLayer* bLL = [[BusinessLayer alloc]init];
FileManager* downloadImages = [FileManager alloc];
for(NSDictionary* inspDocumentResult in documentsList)
{
FloorDocument* floorDocument = [[FloorDocument alloc]init];
floorDocument.docID = [inspDocumentResult objectForKey:#"docID"];
floorDocument.buildingID = selectedBuildingID;
floorDocument.clientID = clientID;
NSDictionary* documentArray = [inspDocumentResult objectForKey:#"Document"];
floorDocument.docType = [[documentArray objectForKey:#"Type"] stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
floorDocument.docScale = [documentArray objectForKey:#"Scale"];
floorDocument.docDescription = [documentArray objectForKey:#"DocDesc"];
//floorDocument.floor = [bLL getFloorNameByDocIDAndBuildingID:selectedBuildingID DocID:floorDocument.docID];
floorDocument.floor = [inspDocumentResult objectForKey:#"Floor"];
NSLog(#"%#",[inspDocumentResult objectForKey:#"hiResImage"]);
[downloadImages downloadInspectionDocuments:[inspDocumentResult objectForKey:#"hiResImage"] ImageName:floorDocument.docID FileType:floorDocument.docType Folder:selectedBuildingID];
NSLog(#"Floor %# - High Res Image copied for %#",floorDocument.floor,floorDocument.docID);
//Download the Low Res Image
NSString* lowResImage = [inspDocumentResult objectForKey:#"lowResImage"];
[downloadImages downloadInspectionDocumentsLowRes:lowResImage ImageName:floorDocument.docID FileType:floorDocument.docType Folder:selectedBuildingID LowResName:#"lowResImage"];
//Copy the Quarter Size File
lowResImage = [lowResImage stringByReplacingOccurrencesOfString:#"LowRes" withString:#"LowRes4"];
[downloadImages downloadInspectionDocumentsLowRes:lowResImage ImageName:floorDocument.docID FileType:floorDocument.docType Folder:selectedBuildingID LowResName:#"lowResImage4"];
NSLog(#"Floor %# - Low Res Images copied for %#",floorDocument.floor,floorDocument.docID);
//Download the tiles
NSArray* tiles = [inspDocumentResult objectForKey:#"lsUrls"];
for(NSString* tile in tiles)
{
#autoreleasepool {
NSArray* tileNameArray = [tile componentsSeparatedByString:#"/"];
if(tileNameArray.count > 0)
{
NSString* destTile = [tileNameArray objectAtIndex:tileNameArray.count-1];
destTile = [destTile stringByReplacingOccurrencesOfString:[NSString stringWithFormat:#".%#",floorDocument.docType] withString:#""];
NSLog(#"TileName:%#",destTile);
[downloadImages downloadInspectionDocumentsTiles:tile ImageName:floorDocument.docID FileType:floorDocument.docType Folder:selectedBuildingID TileName:destTile];
}
}
}
NSLog(#"Floor %# - Tiles Image copied for %#",floorDocument.floor,floorDocument.docID);
NSLog(#"Downloading Documents Tiles For %# Completed at %#",floorDocument.docID,[bLL getCurrentDate]);
[bLL saveFloorDocuments:floorDocument IsUpdate:isUpdate];
// downloadImages=nil;
}
bLL = nil;
}
please help me out in this problem.`
This is the code which I am using inside the DownloadInspectionDocuments:-
-(void)downloadInspectionDocuments:(NSString *)url ImageName:(NSString *)imageName FileType:(NSString*)fileType Folder:(NSString*)folder
{
#autoreleasepool
{
NSString* source =[FileManager getInspectionDocumentsFolder];
//Lets get the destination folder
NSString *destination = [NSString stringWithFormat:#"%#/%#/%#",source,folder,imageName];
[self createFolder:destination CreateSubFolders:true];
NSString *filePath = [NSString stringWithFormat:#"%#/%#.%#",destination,imageName,fileType];
NSFileManager* fm = [[NSFileManager alloc]init];
if(![fm fileExistsAtPath:filePath])
{
NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
[data1 writeToFile:filePath atomically:YES];
}
}
// return [NSString stringWithFormat:#"%#.%#",imageName,fileType];
}
ARC isn't garbage collection: it will insert the memory management code (retain/release) for you but you still need to make sure you are not using up too many resources (in the same way you would in non-ARC code).
You are running this large loop on the main thread, so any memory being consumed is not going to be freed until the next run loop.
You need to break this function down into smaller steps that can be carried out in stages.
For now, if there isn't too much memory consumed for a single iteration of the outer-loop of the function you can add an autorelease pool at that level (I see you have on on the inner loop)
for(NSDictionary* inspDocumentResult in documentsList)
{
#autoreleasepool {
.... remaining code goes here
}
}
and it will at least drain what it can each iteration.
Given that you are downloading a large number of files and will be relying on network connectivity I would recommend performing the downloads asynchronously though. If you haven't already, check out AFNetworking to simplify this. This will give you much more control over your resources than you are getting now with a resource-intensive blocking call on the main thread.
You can save yourself a lot of work by following davbryn's and Andrea's suggestions to use AFNetworking and stream the file. Basically, don't put the whole file in memory and then write it to disk, write to disk as you get the bytes from the network. This should reduce the pressure on memory. For example:
- (void)downloadFile:(NSString *)urlString {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
NSString *destinationPath = [NSDocumentDirectory() stringByAppendingPathComponent:#"some-file-name"];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setOutputStream:[NSOutputStream outputStreamToFileAtPath:destinationPath append:NO]];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Super duper awesome!");
// Maybe start another download here?
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error downloading file: %#", error);
}];
[operation start];
}
So then all you need to do is generate the list of things to download and in your success block start downloading another file.

How to know if a PHAsset has been modified?

More specifically, how can you know whether a PHAsset has current version of the underlying asset different than the original?
My user should only need to choose between the current or original asset when necessary. And then I need their answer for PHImageRequestOptions.version.
As of iOS 16, PHAsset has a hasAdjustments property which indicates if the asset has been edited.
For previous releases, you can get an array of data resources for a given asset via PHAssetResource API - it will have an adjustment data resource if that asset has been edited.
let isEdited = PHAssetResource.assetResources(for: asset).contains(where: { $0.type == .adjustmentData })
Note that if you want to actually work with a resource file, you have to fetch its data using a PHAssetResourceManager API. Also note that this method returns right away - there's no waiting for an async network request, unlike other answers here.
I have found two ways of checking PHAsset for modifications.
- (void)tb_checkForModificationsWithEditingInputMethodCompletion:(void (^)(BOOL))completion {
PHContentEditingInputRequestOptions *options = [PHContentEditingInputRequestOptions new];
options.canHandleAdjustmentData = ^BOOL(PHAdjustmentData *adjustmentData) { return YES; };
[self requestContentEditingInputWithOptions:options completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
if (completion) completion(contentEditingInput.adjustmentData != nil);
}];
}
- (void)tb_checkForModificationsWithAssetPathMethodCompletion:(void (^)(BOOL))completion {
PHVideoRequestOptions *options = [PHVideoRequestOptions new];
options.deliveryMode = PHVideoRequestOptionsDeliveryModeFastFormat;
[[PHImageManager defaultManager] requestAVAssetForVideo:self options:options resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
if (completion) completion([[asset description] containsString:#"/Mutations/"]);
}];
}
EDIT: I was at the point where I needed the same functionality for PHAsset with an image. I used this:
- (void)tb_checkForModificationsWithAssetPathMethodCompletion:(void (^)(BOOL))completion {
[self requestContentEditingInputWithOptions:nil completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
NSString *path = (contentEditingInput.avAsset) ? [contentEditingInput.avAsset description] : contentEditingInput.fullSizeImageURL.path;
completion([path containsString:#"/Mutations/"]);
}];
}
Take a look at PHImageRequestOptionsVersion
PHImageRequestOptionsVersionCurrent
Request the most recent version of the image asset (the one that reflects all edits).
The resulting image is the rendered output from all previously made adjustments.
PHImageRequestOptionsVersionUnadjusted
Request a version of the image asset without adjustments.
If the asset has been edited, the resulting image reflects the state of the asset before any edits were performed.
PHImageRequestOptionsVersionOriginal
Request the original, highest-fidelity version of the image asset. The
resulting image is originally captured or imported version of the
asset, regardless of any edits made.
If you ask user before retrieving assets, you know which version user specified. If you get a phasset from elsewhere, you can do a revertAssetContentToOriginal to get the original asset. And PHAsset has modificationDate and creationDate properties, you can use this to tell if a PHAsset is modified.
I found this code the only one working for now, and it handles most of the edge cases. It may not be the fastest one but works well for most images types. It takes the smallest possible original and modified image and compare their data content.
#implementation PHAsset (Utilities)
- (void)checkEditingHistoryCompletion:(void (^)(BOOL edited))completion
{
PHImageManager *manager = [PHImageManager defaultManager];
CGSize compareSize = CGSizeMake(64, 48);
PHImageRequestOptions *requestOptions = [PHImageRequestOptions new];
requestOptions.synchronous = YES;
requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat;
requestOptions.version = PHImageRequestOptionsVersionOriginal;
[manager requestImageForAsset:self
targetSize:compareSize
contentMode:PHImageContentModeAspectFit
options:requestOptions
resultHandler:^(UIImage *originalResult, NSDictionary *info) {
UIImage *currentImage = originalResult;
requestOptions.version = PHImageRequestOptionsVersionCurrent;
[manager requestImageForAsset:self
targetSize:currentImage.size
contentMode:PHImageContentModeAspectFit
options:requestOptions
resultHandler:^(UIImage *currentResult, NSDictionary *info) {
NSData *currData = UIImageJPEGRepresentation(currentResult, 0.1);
NSData *orgData = UIImageJPEGRepresentation(currentImage, 0.1);
if (completion) {
//handle case when both images cannot be retrived it also mean no edition
if ((currData == nil) && (orgData == nil)) {
completion(NO);
return;
}
completion(([currData isEqualToData:orgData] == NO));
}
}];
}];
}
#end

Resources