Optimizing an array of UIImages - ios

So I'm building an app that the user takes pictures of themselves, it saves them to the camera roll, and I'm saving references to the asset URLs to display them in the app. At first this model seemed to work fine, but as I took more and more pictures it started receiving memory warnings and eventually crashed. Is there a better way to approach this?
This is how I load up the saved photos at the launch of the app (which freezes the app for up to 10 seconds depending on how many are being loaded):
- (void) loadPhotosArray
{
_photos = [[NSMutableArray alloc] init];
NSData* data = [[NSUserDefaults standardUserDefaults] objectForKey: #"savedImages"];
if (data)
{
NSArray* storedUrls = [[NSArray alloc] initWithArray: [NSKeyedUnarchiver unarchiveObjectWithData: data]];
// reverse array
NSArray* urls = [[storedUrls reverseObjectEnumerator] allObjects];
for (NSURL* assetUrl in urls)
{
// Block to handle image handling success
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
{
ALAssetRepresentation *rep = [myasset defaultRepresentation];
CGImageRef iref = [rep fullResolutionImage];
if (iref) {
UIImage* tempImage = [UIImage imageWithCGImage:iref];
UIImage* image = [[UIImage alloc] initWithCGImage: tempImage.CGImage scale: 1.0 orientation: UIImageOrientationRight];
// Set image in imageView
[_photos addObject: image];
[[NSNotificationCenter defaultCenter] postNotificationName: #"PhotosChanged" object: self];
}
};
// Handles failure of getting image
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror)
{
NSLog(#"Can't get image - %#",[myerror localizedDescription]);
};
// Load image then call appropriate block
ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
[assetslibrary assetForURL: assetUrl
resultBlock: resultblock
failureBlock: failureblock];
}
}
else
{
NSLog(#"Photo storage is empty");
}
}
And saving photos:
- (void) addImageToPhotos: (UIImage*)image
{
// Store image at front of array
NSMutableArray* temp = [[NSMutableArray alloc] initWithObjects: image, nil];
// load rest of images onto temp array
for (UIImage* image in _photos)
{
[temp addObject: image];
}
_photos = nil;
_photos = [[NSMutableArray alloc] initWithArray: temp];
// [self.photos addObject: image];
[[NSNotificationCenter defaultCenter] postNotificationName: #"PhotosChanged" object: self.photos];
// save to cache
ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];
[library saveImage: image toAlbum: #kAlbumeName withCompletionBlock:^(NSError *error) {
if (error)
{
NSLog(#"Error saving");
}
}];
}

I think have 2 methods to optimize this problem.
U should just save image name string instead of saving UIImage object, then when need to display the image, use pagination to display image according to saved image name string.
U should use multi-thread to deal with this long time task, recommend u to use gcd to load image name string.

Related

Image Metadata from Action Extension

Usually when i want to read some image metadata in iOS i use imagePickerController to choose the image and Photos Framework and imagePickerController:didFinishPickingMediaWithInfo:
to get image info and extract the metadata like this
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
UIImagePickerControllerSourceType pickerType = picker.sourceType;
if(pickerType == UIImagePickerControllerSourceTypePhotoLibrary)
{
NSURL *url = [info objectForKey:UIImagePickerControllerReferenceURL];
PHFetchResult *fetchResult = [PHAsset fetchAssetsWithALAssetURLs:#[url,] options:nil];
PHAsset *asset = fetchResult.firstObject;
[self metaDataFromPhotoLibrary:asset];
[self dismissViewControllerAnimated:YES completion:NULL];
}
}
-(void)metaDataFromPhotoLibrary:(PHAsset*)asset{
// NSLog(#"Start metadata");
PHContentEditingInputRequestOptions *options = [[PHContentEditingInputRequestOptions alloc] init];
options.networkAccessAllowed = YES; //download asset metadata from iCloud if needed
[asset requestContentEditingInputWithOptions:options completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
CIImage *fullImage = [CIImage imageWithContentsOfURL:contentEditingInput.fullSizeImageURL];
NSDictionary *metadata = fullImage.properties;
NSMutableDictionary *imageMetadata = nil;
imageMetadata = [[NSMutableDictionary alloc] initWithDictionary:metadata];
NSString *dateString = metadata [#"{TIFF}"] [#"DateTime"];
NSString *latitude = metadata [#"{GPS}"][#"Latitude"];
NSString *longitude = metadata [#"{GPS}"][#"Longitude"];
//etc etc etc
}
But can't do the same thing from an Action Extension
in the extension code i use a code like this to get the selected image
- (void)viewDidLoad {
[super viewDidLoad];
// Get the item[s] we're handling from the extension context.
// For example, look for an image and place it into an image view.
// Replace this with something appropriate for the type[s] your extension supports.
BOOL imageFound = NO;
for (NSExtensionItem *item in self.extensionContext.inputItems) {
for (NSItemProvider *itemProvider in item.attachments) {
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeImage]) {
// This is an image. We'll load it, then place it in our image view.
__weak UIImageView *immagine = self.immagine;
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeImage options:nil completionHandler:^(UIImage *image, NSError *error) {
if(image) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[immagine setImage:image];
}];
}
}];
imageFound = YES;
break;
}
}
if (imageFound) {
// We only handle one image, so stop looking for more.
break;
}
}
}
Using UIImagePNGRepresentation or UIImageJPEGRepresentation i lost many metadata and i can read only a few data !
How can i get all image metadata from the image selected from action Extension ?
Thank you so mush
Note: i 've found some app in the AppsStore that read all metadata dictionary from extension , so there must be a solution for my problem !
Thank you again
Vanni

in iOS How to apply lazy loading while fetching images and videos from assets?

I have fetched the images and videos from gallery, it works fine when the number of videos and images is minimum,but when the number of images and videos is large, it blocks the main thread.
-(void)viewDidAppear:(BOOL)animated{
[NSThread sleepForTimeInterval:1];
// call the same method on a background thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self getAllVideosAndImages];
});
}
To gat Images and Videos From Asset
-(void)getAllVideosAndImages{
NSMutableArray *assetGroups = [[NSMutableArray alloc] init];
assetGroups = [[NSMutableArray alloc] init];
library = [[ALAssetsLibrary alloc] init];
NSUInteger groupTypes = ALAssetsGroupAll;
void (^assetEnumerator)
( ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *result, NSUInteger index, BOOL *stop) {
if(result != nil) {
if([[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypeVideo]||[[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypePhoto]) {
ALAssetRepresentation *rep = [result defaultRepresentation];
CGImageRef iref = [rep fullResolutionImage];
if (iref) {
//GET DATE
NSDate *myDate = [result valueForProperty:ALAssetPropertyDate];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"MMM dd,YYYY"];
NSString *realDateStr = [dateFormatter stringFromDate:myDate];
//GET IMG AND VIDEO
UIImage *largeimage = [UIImage imageWithCGImage:iref];
NSData *webData = UIImagePNGRepresentation(largeimage);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *localFilePath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"img%zd.mov",imgName]];
imgName=imgName+1;
[webData writeToFile:localFilePath atomically:YES];
Para_ImageSet *objImgSet=[Para_ImageSet new];
objImgSet.imagePath=localFilePath;
objImgSet.imageDate=realDateStr;
// [yArrayOfAllImges addObject:objImgSet];
if ([[result valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypeVideo]) {
objImgSet.is_video=YES;
}
else
{
objImgSet.is_video=NO;
}
if ([tempDate isEqualToString:#""]) {
yArrayOfImagesWithDate=[NSMutableArray new];
[yArrayOfImagesWithDate addObject:objImgSet];
tempDate=realDateStr;
}else
{
//GATE IMAGE COUNT
if ([realDateStr isEqualToString:tempDate]) {
//DO NOTHING
[yArrayOfImagesWithDate addObject:objImgSet];
}
else
{
[yArrayOFNNestedImgs addObject:yArrayOfImagesWithDate];
NSLog(#" ----------ImagesWithDate =%zd, %#",yArrayOfImagesWithDate.count,tempDate);
yArrayOfImagesWithDate=[NSMutableArray new];
[yArrayOfImagesWithDate addObject:objImgSet];
tempDate=realDateStr;
}
}
}
}
}
[self.tableView reloadData];
};
void (^ assetGroupEnumerator) ( ALAssetsGroup *, BOOL *)= ^(ALAssetsGroup *group, BOOL *stop) {
if(group != nil) {
[group enumerateAssetsUsingBlock:assetEnumerator];
[assetGroups addObject:group];
// NSNumber *countObj = [NSNumber numberWithInt:count];
NSLog(#" ----------ImagesWithDate =%zd, %#",yArrayOfImagesWithDate.count,tempDate);
[yArrayOFNNestedImgs addObject:yArrayOfImagesWithDate];
NSLog(#" ----------Count =%zd",yArrayOFNNestedImgs.count);
[NSThread sleepForTimeInterval:10];
// update UI on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
};
[library enumerateGroupsWithTypes:groupTypes
usingBlock:assetGroupEnumerator
failureBlock:^(NSError *error) {NSLog(#"A problem occurred");}];
}
Please Suggest..Thanks in advance.
[NSThread sleepForTimeInterval:1]; is blocking main thread for a second. It's not needed.
You could use [self performSelectorInBackground:#selector(getAllVideosAndImages) withObject:nil];
Also, wrap first [self.tableView reloadData]; in
dispatch_async(dispatch_get_main_queue(), ^{
...
}); too
In order to lazily load the resources, here is the recommended approach:
Download URLs of all resources and store them into your container (array / objects etc)
Fire a NSURLSessionTask (post iOS 7 only) which runs async on background queue. If you are below iOS 7, you can use NSURLConnection SendAsynchronousRequest API - it's deprecated in iOS 9 so you better get rid of that soon.
Process your downloaded resources while on background queue - store audio/image files, create UIImage from NSData, and so on.
Come back to main queue, then update the relevant UI part with downloaded content. If audio/video, play it. If UIImage, display it.
Here - the entire approach is described in my tutorial.

App is terminated due to memory pressure while taking multiple pictures

I have tried a lot of solutions provided on other questions which were same as mine but nothing could help me much.
Let me tell you what am I doing. I have a collection view. In that I will display some images which will be captured by camera. I am capturing multiple pictures at a time. All the pictures which I have taken, the address of those images will first save into database and then those images will be displayed in collection view.
Now what happens, when I click 40-50 images at a time, the app is crashed and xcode displays a message something like "app is terminating due to memory pressure". Also I am getting too many memory warnings in logs but actually I was neglecting them.
First I am writing code for taking multiple pictures-
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
//Get Image URL from Library
NSString *mediaType = [info objectForKey:UIImagePickerControllerMediaType];
NSURL *urlPath = [info valueForKey:UIImagePickerControllerReferenceURL];
if (segmentControl.selectedSegmentIndex != 1) {
[picker dismissViewControllerAnimated:YES completion:nil];
}
if (segmentControl.selectedSegmentIndex == 2) {
[self insertPicToDB:urlPath];
}else{
__block NSURL *url;
if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {
UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
// Request to save the image to camera roll
[library writeImageToSavedPhotosAlbum:[image CGImage] orientation:(ALAssetOrientation)[image imageOrientation] completionBlock:^(NSURL *assetURL, NSError *error){
if (error) {
} else {
url = assetURL;
[self insertPicToDB:url];
}
}];
}
}
}
}
After while taking each picture, I am saving image url in db and then at the same time trying to reload the collection view as well.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"Cell";
collectionCell = (CollectionCell *)[_collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
collectionCell.imageView = (UIImageView*)[collectionCell viewWithTag:100];
collectionCell.imageView.image = [UIImage imageNamed:#"placeholder.png"];
NSString *fileURL = [[recipeImages[indexPath.section] objectAtIndex:indexPath.item] objectForKey:#"FileUrl"];
collectionCell.imagURL = fileURL;
if ([fileURL hasPrefix:#"assets-library"]) {
[self getImageFromURL:[NSURL URLWithString:fileURL] :indexPath];
}else{
fileURL = [NSString stringWithFormat:#"%#/uploads/thumbnail/%#",[[HttpClient sharedInstance]getBaseURLString],[[fileURL componentsSeparatedByString:#"\\"]lastObject]];
[collectionCell.imageView setImageWithURL:[NSURL URLWithString:fileURL] placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
}
return collectionCell;
}
So the condition is, I will keep clicking the pictures and the pictures will be saving in background.
And the definition of method "getImageFromURL" is-
-(void)getImageFromURL:(NSURL*)yourUrl :(NSIndexPath*)indexPath{
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
{
ALAssetRepresentation *rep = [myasset defaultRepresentation];
#autoreleasepool {
CGImageRef iref = [rep fullScreenImage];
if (iref) {
UIImage *image = [UIImage imageWithCGImage:iref];
dispatch_async(dispatch_get_main_queue(), ^{
collectionCell = (CollectionCell*)[_collectionView cellForItemAtIndexPath:indexPath];
if (collectionCell) {
NSData *imageData = UIImageJPEGRepresentation(image, 0.1);
UIImage *compressedImage = [UIImage imageWithData:imageData];
collectionCell.imageView.image = compressedImage;
}else{
collectionCell.imageView.image = nil;
}
[collectionCell.imageView setNeedsDisplay];
[collectionCell setNeedsDisplay];
});
iref = nil;
}
}
};
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror)
{
NSLog(#"Can't get image - %#",[myerror localizedDescription]);
};
ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
[assetslibrary assetForURL:yourUrl
resultBlock:resultblock
failureBlock:failureblock];
}
I am also trying to compress the images while fetching in collection view. So I don't think that it is crashing because of collection view. What can be the reason? Is it because of I am using ALAssetsLibrary or something else?
I was debugging it in iPhone 4S with iOS version 7.1.1.
Thanks in advance.

Go from image url rep from asset library to base64

I am ultimately trying to convert an array of photos that are in a dictionary in url rep form to base64 to be sent over json.
Here is the dictionary code and log of it:
NSDictionary *dict = [self.form dictionaryWithValuesForKeys:keys];
NSLog(#"dict::%#",dict);
NSLog:
dict::{
boardLodgingFurnished = "<null>";
caption = "<null>";
cars = "";
photos = (
{
caption = "";
urlRep = "assets-library://asset/asset.JPG?id=CE8A426B-3B59-4172-8761-CC477F3BB3EE&ext=JPG";
},
{
caption = "";
urlRep = "assets-library://asset/asset.JPG?id=F4B68A42-1CA0-4880-9FB5-177CB091A28C&ext=JPG";
}
);
yearsAtLocation = "";
yearsInTheBusiness = "";
}
So for each photo in the dictionary I would like to take the urlRep and convert that to a base64 string and replace the urlRep with it in the dictionary.
What I have right now..not sure if I am going in the right direction:
for (id imageURL in [dict objectForKey:#"photos"])
{
ALAssetsLibrary *library = [ALAssetsLibrary new];
ALAsset *ourAsset = [self assetForURL:imageURL withLibrary:library];
/* Check out ALAssets */
NSLog(#"%#", ourAsset);
ALAssetRepresentation *representation = [ourAsset defaultRepresentation];
CGImageRef imageRef = [representation fullResolutionImage];
//TODO: Deal with JPG or PNG
NSData *imageData = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.1);
NSLog(#"imagedata??%#", [imageData base64EncodedString]);
//need to know how to add this back to dict
}
the below method is called from above but crashes on the while loop with
-[__NSDictionaryI scheme]: unrecognized selector sent to instance 0x166dd090
2014-01-03 10:57:27.361 Inspection App[2728:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSDictionaryI scheme]: unrecognized selector sent to instance 0x166dd090'
method
- (ALAsset *)assetForURL:(NSURL *)url withLibrary:(ALAssetsLibrary *)assetsLibrary {
__block ALAsset *result = nil;
__block NSError *assetError = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[assetsLibrary assetForURL:url resultBlock:^(ALAsset *asset) {
result = asset;
dispatch_semaphore_signal(sema);
} failureBlock:^(NSError *error) {
assetError = error;
dispatch_semaphore_signal(sema);
}];
if ([NSThread isMainThread]) {
while (!result && !assetError) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
else {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
return result;
}
edit:
if (photoUrls.count) {
for (id photos in photoUrls){
NSString *urlString = photos;
[self base64ImageAtUrlString:urlString result:^(NSString *base64) {
NSLog(#"imagedata??%#", base64);
}];
}
}
else {
NSLog(#"where are my urls?");
}
NSMutableDictionary *jsonWithPhotos = [dict mutableCopy];
[jsonWithPhotos setObject:convertedImages forKey:#"photo64"];
NSLog(#"jjson photos::%#", jsonWithPhotos);
updated method
- (void)base64ImageAtUrlString:(NSString *)urlString result:(void (^)(NSString *))completion {
NSURL *url = [NSURL URLWithString:urlString];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:url resultBlock:^(ALAsset *asset) {
// borrowing your code, here... didn't check it....
ALAssetRepresentation *representation = [asset defaultRepresentation];
CGImageRef imageRef = [representation fullResolutionImage];
//TODO: Deal with JPG or PNG
NSData *imageData = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.1);
NSString *base64 = [imageData base64EncodedString];
completion(base64);
[convertedImages addObject:base64];
// NSLog(#"converted::%#",convertedImages);
} failureBlock:^(NSError *error) {
NSLog(#"that didn't work %#", error);
}];
}
When i log jsonWithPhotos the object photo64 is just a blank array
The crash is due to a false assumption in the code about the dictionary. Given the posted description of the dictionary parsed as json, you'd need to get the urls like this:
// collect the photo urls in an array
NSMutableArray *photoUrls = [NSMutableArray array];
// photos is an array of dictionaries in the dictionary
NSArray *photos = dict[#"photos"];
for (NSDictionary *photo in photos) {
// photo is a dictionary containing a "caption" and a "urlRep"
[photoUrls addObject:photo[#"urlRep"]];
}
Now you can proceed with a method whose job is just the conversion. Your question might contain a lot more questions about how to do this. I'd recommend starting simple. See if you can do one conversion. Test it by writing the reverse, from base64 back to an image.
Edit 0: Without deeply checking it, I'd restructure your encoding attempt to look like this:
- (void)base64ImageAtUrlString:(NSString *)urlString result:(void (^)(NSString *))completion {
NSURL *url = [NSURL URLWithString:urlString];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:url resultBlock:^(ALAsset *asset) {
// borrowing your code, here... didn't check it....
ALAssetRepresentation *representation = [asset defaultRepresentation];
CGImageRef imageRef = [representation fullResolutionImage];
//TODO: Deal with JPG or PNG
NSData *imageData = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.1);
NSString *base64 = [imageData base64EncodedString];
completion(base64);
} failureBlock:^(NSError *error) {
NSLog(#"that didn't work %#", error);
}];
}
Call it like this:
if (photoUrls.count) {
NSString *urlString = photoUrls[0];
[self base64ImageAtUrlString:urlString result:^(NSString *base64) {
NSLog(#"imagedata??%#", base64);
}];
} else {
NSLog(#"where are my urls?");
}
Once it's working, see if you can reverse it, making an image out of the base64 data. Finally, once all that's working, you can deal with potential memory issues. My advice there is to consider encoding one at a time, post one at a time to the server and release everything in between.
Edit 1 - Per followup question, if you want to replace all of the urls in the url array with base64 encodings, it might go something like this (remember that this might use a lot of memory):
- (void)base64ImagesAtUrls:(NSMutableArray *)urls result:(void (^)(void))completion {
__block NSInteger completed = 0; // this is how we'll know that we're done
// this approach doesn't depend on the asset library retrievals completing
// sequentially, even though they probably will
for (int i=0; i<urls.count; i++) {
NSString *urlString = urls[i];
[self base64ImageAtUrlString:urlString result:^(NSString *base64) {
[urls replaceObjectAtIndex:i withObject:base64];
if (++completed == urls.count) completion();
}];
}
}

Running background threads that include blocks

I have a singleton that loads up an a bunch of ALAssets when my app launches. This is causing the main thread to freeze for more then 10 seconds while it loads each image into memory. Obviously a big no no.
I tried to put it on a background thread, but it only partially executes.
+ (CCPhotos*) sharedPhotos
{
static CCPhotos* shared = nil;
if (!shared)
{
shared = [[CCPhotos alloc] init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[shared loadPhotosArray];
});
}
return shared;
}
- (void) loadPhotosArray
{
NSLog(#"Loading photos");
_photos = [[NSMutableArray alloc] init];
NSData* data = [[NSUserDefaults standardUserDefaults] objectForKey: #"savedImages"];
if (data)
{
NSArray* storedUrls = [[NSArray alloc] initWithArray: [NSKeyedUnarchiver unarchiveObjectWithData: data]];
// reverse array
NSArray* urls = [[storedUrls reverseObjectEnumerator] allObjects];
for (NSURL* assetUrl in urls)
{
NSLog(#"Looking up %#", assetUrl);
// Block to handle image handling success
// This initializes, but doesn't get called
////-->>
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
{
ALAssetRepresentation *rep = [myasset defaultRepresentation];
CGImageRef iref = [rep fullScreenImage];
if (iref) {
UIImage* tempImage = [UIImage imageWithCGImage:iref];
UIImage* image = [[UIImage alloc] initWithCGImage: tempImage.CGImage scale: 1.0 orientation: UIImageOrientationUp];
// Set image in imageView
[_photos addObject: image];
NSLog(#"Added photo with url: %#", [rep url]);
[[NSNotificationCenter defaultCenter] postNotificationName: #"PhotosChanged" object: self];
}
};
// Handles failure of getting image
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror)
{
NSLog(#"Can't get image - %#",[myerror localizedDescription]);
};
// Load image then call appropriate block
ALAssetsLibrary* assetslibrary = [[ALAssetsLibrary alloc] init];
[assetslibrary assetForURL: assetUrl
resultBlock: resultblock
failureBlock: failureblock];
}
}
else
{
NSLog(#"Photo storage is empty");
}
}
I've narrowed down the problem to ALAssetsLibraryAssetForURLResultBlock resultblock which doesn't get called. Multiple threads spawn at the beginning, and each one gets to this line, initializes the result block, but doesn't call it. I think it has to do with the thread safety on the block. Any thoughts?
Try doing this when you post the notification:
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName: #"PhotosChanged" object: self];
});
I believe the reason it is not working for you is because this is not being called on the main thread. All UI changes/updates (which I am assuming this leads to) must be executed on the main thread. This will force the notification to happen on the necessary thread and should work properly!
This is a pretty common thing to do. I actually created a code snippet because I found myself typing this out so often:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
<#on back thread#>
dispatch_async(dispatch_get_main_queue(), ^{
<#on main thread#>
});
});
I then set the completion short cut to dispatch_async
From what I'm understanding, you are spinning up multiple instances of your singleton. In that case you will probably want to put a #synchronized block in there. For example:
+ (CCPhotos*) sharedPhotos
{
static CCPhotos* shared = nil;
#synchronized ([CCPhotos class])
{
if (!shared)
{
shared = [[CCPhotos alloc] init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[shared loadPhotosArray];
});
}
}
return shared;
}
Hope that helps.

Resources