Load images asyncroniously to the array using GCD - ios

I try to use panoramaGL framework to show panorama. The server-side returns several tile images for every side of cube panorama. So I need to load this images asyncroniously into the array and after this I need to make a big side texture from this images - the code for the first part of this task is:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
int columnCount;
int rowCount;
NSString *side;
if ([level isEqual: #"3"]) {
columnCount = 1;
rowCount = 1;
} else if ([level isEqual: #"2"]) {
columnCount = 2;
rowCount = 2;
... here I prepare the url string parameters
if (face == PLCubeFaceOrientationFront)
side = #"F";
else if (face == PLCubeFaceOrientationBack)
side = #"B";
... here I prepare the url string parameters
self.tileArray = [[NSMutableArray alloc] initWithCapacity:columnCount];
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
NSString *tileUrl = [NSString stringWithFormat:#"%d_%d", columnIndex, rowIndex];
NSMutableArray *tileUrlComponents = [NSMutableArray array];
[tileUrlComponents addObject: panoramaData[#"id"]];
[tileUrlComponents addObject: side];
[tileUrlComponents addObject: level];
[tileUrlComponents addObject: tileUrl];
NSString *tileIdString = [tileUrlComponents componentsJoinedByString:#"/"];
NSString *panoramaIdUrlString = [NSString stringWithFormat:#"http://SOMEURL=%#", tileIdString];
NSURL *url = [NSURL URLWithString:panoramaIdUrlString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:req queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *res, NSData *data, NSError *err) {
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray *array = [self.tileArray objectAtIndex:columnIndex];
[array replaceObjectAtIndex:rowIndex withObject:image];
[self.tileArray addObject:array];
});
}];
NSLog(#"The URL for the %# tile is %#", tileUrl, tileIdString);
}
}
});
The main question - how can I understand, that my array is loaded - and I can now work with the images in it. The second question is that I get the empty array now unfortunately. Any help?

You can do something like this
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
dispatch_sync(dispatch_get_main_queue(), ^{
NSData *data = [[NSData alloc] initWithContentsOfURL: ImageURL];
UIImage *image = [[UIImage alloc] initWithData: data];
[UIImageJPEGRepresentation(image, 100) writeToFile: imagePath atomically: YES];
});
});
where imagePath ia a path in your applications directory..You can make a temporary directory and store the images there…and while retrieving you can just use the simple method as..
UIImage *image=[UIImage imageWithCOntentsOFFile:imagePath];.
It worked for me…just give it a try

I am not sure you considered using operations.
This is how I would do:
Create NSOperation for each request (make sure in your code the operation stays alive until it has finished loading)
Add each operation to NSOperationQueue. (By calling -operations you can see how many operations are in progress)
If you implement NSOperationQueue on back thread you can use -waitUntilAllOperationsAreFinished (this method blocks current thread until all your images have been downloaded) and then execute your code to display images. You can alternatively add all NSOperation's to NSArray and add to NSOperationQueue by using -(void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait

Related

UI Gets Blocked When Using CGImageSourceCreateWithURL and CGImageSourceCopyPropertiesAtIndex

I am checking the dimensions of images via their URLs stored in an array called destination_urls. I then calculate the height of images if they were stretched to the width of the screen while maintaining the aspect ratio. All of this is done in a for loop.
When the code is run, the app UI gets stuck during the for loop. How can I revise the code to make sure the app doesn't freeze?
int t = 0;
int arraycount = [_destination_urls count];
dispatch_async(dispatch_get_main_queue(), ^{
for (int i = 0; i < arraycount; i++) {
NSURL *imageURL = [NSURL URLWithString:[_destination_urls[i] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
CGImageSourceRef imgSource = CGImageSourceCreateWithURL((__bridge CFURLRef)imageURL, NULL);
NSDictionary* imageProps = (__bridge_transfer NSDictionary*) CGImageSourceCopyPropertiesAtIndex(imgSource, 0, NULL);
NSString *imageHeighttoDisplay = [imageProps objectForKey:#"PixelHeight"];
originalimageHeight = [imageHeighttoDisplay intValue];
NSString *imageWidthtoDisplay = [imageProps objectForKey:#"PixelWidth"];
originalimageWidth = [imageWidthtoDisplay intValue];
if (imgSource){
_revisedImageURLHeight[t] = [NSNumber numberWithInt:(screenWidth)*(originalimageHeight/originalimageWidth)];
t = t +1;
CFRelease(imgSource);
}
}
});

it happened a memory leaks when I getting image exif dictionary from a asset object using a static method,

Following are the method an methods call tree that cause the memory leak
//get the exif info of image asset background
#autoreleasepool {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
self.dataSource = [NSMutableArray new];
__weak typeof(self) weakSelf = self;
dispatch_async(queue, ^{
for (int i = 0; i < self.selectImageList.count; i++) {
PFALAssetImageItemData *dataEntity = weakSelf.selectImageList[i];
//getting the object usering an image asset
ImageExifInfoEntity *imageExifEntity = [ImageExifInfoEntity getAlbumImageFromAsset:dataEntity.imageAsset imageOryder:i];
LOG(#"%#",imageExifEntity.description);
[weakSelf.dataSource addObject:imageExifEntity];
}
//back main thread update views
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
[self hideHud];
});
});
}
In this code I want to creat ImageExifInfoEntity using a static method with an asset in a thread:
[ImageExifInfoEntity getAlbumImageFromAsset:dataEntity.imageAsset imageOryder:i];
In this method,it create an new object of ImageExifInfoEntity type,and get the exif Dictionary using a static method
+(ImageExifInfoEntity *)getAlbumImageFromAsset:(ALAsset *)asset category:(NSString *)category imageOryder:(NSInteger)imageOrder{
ImageExifInfoEntity *albumImage = [ImageExifInfoEntity new];
..........
albumImage.imageSize = [UIImage imageSizeWithAlasset:asset];
albumImage.exifDic = [ImageExifInfoEntity getExifInfoFromAsset:asset] == nil ? #{}:[ImageExifInfoEntity getExifInfoFromAsset:asset];
..........
}
Finally,I get an exif dictionary using this method where the memory leak happed
+(NSDictionary *)getExifInfoFromAsset:(ALAsset *)asset {
NSDictionary *_imageProperty;
__weak ALAsset *tempAsset = asset;
ALAssetRepresentation *representation = tempAsset.defaultRepresentation;
uint8_t *buffer = (uint8_t *)malloc(representation.size);
NSError *error;
NSUInteger length = [representation getBytes:buffer fromOffset:0 length:representation.size error:&error];
NSData *data = [NSData dataWithBytes:buffer length:length];
CGImageSourceRef cImageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(cImageSource, 0, NULL);
_imageProperty = (__bridge_transfer NSDictionary*)imageProperties;
free(buffer);
NSLog(#"image property: %#", _imageProperty);
return _imageProperty;
}
here is the instrument analyze result
call tree
the final method that cause memory leaks
CGImageSourceCreateWithData() is a creation function.
CGImageSourceRef cImageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
According to the CF memory rules, you have to release it with CFRelease().
replace __bridge with __bridge_transfer so that ARC will be in charge of freeing the memory
I think you should call CFRelease(cImageSource); to release the image source. See document: https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CGImageSource/#//apple_ref/c/func/CGImageSourceCreateWithData
An image source. You are responsible for releasing this object using CFRelease.

Prevent loop from looping until inner loop writes to disk

I have a nested for loop in where I call a getSnapShotData method many times and write this data to disk. I've noticed I get too much memory build up doing this and I run out of memory, so I thought this would be a good use case for using dispatch semaphore.
I'm still running out of memory, so I'm not sure if I'm using the semaphore properly. Essentially I want the next loop to wait until the prior loop's data is written to disk, as I think that will free the memory. But I could be wrong. Thank you for your help.
Code:
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
for (NSDictionary *sub in self.array)
{
NSArray *lastArray = [sub objectForKey:#"LastArray"];
for (NSDictionary *dict in lastArray)
{
currentIndex ++;
NSData *frame = [NSData dataWithData:[self getSnapshotData]];
savePath = [NSString stringWithFormat:#"%#/%lu.png",frameSourcePath,(unsigned long)currentIndex];
BOOL nextLoop = [frame writeToFile:savePath options:0 error:nil];
frame = nil;
if (nextLoop)
{
dispatch_semaphore_signal(sema);
}
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
}
- (NSData *)getSnapshotData
{
UIGraphicsBeginImageContextWithOptions(self.containerView.bounds.size, NO, 0.0);
[self.containerView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *snapShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return [NSData dataWithData:UIImagePNGRepresentation(snapShot)];
}
You have too many autoreleased objects. Add an autorelease pool to improve the situation instead of using the semaphore.
for (NSDictionary *sub in self.array)
{
NSArray *lastArray = [sub objectForKey:#"LastArray"];
for (NSDictionary *dict in lastArray)
{
#autoreleasepool {
currentIndex ++;
NSData *frame = [NSData dataWithData:[self getSnapshotData]];
savePath = [NSString stringWithFormat:#"%#/%lu.png",frameSourcePath,(unsigned long)currentIndex];
BOOL nextLoop = [frame writeToFile:savePath options:0 error:nil];
}
}
}

How to get panoramas from ALAsset objects

We have about 2k objects, which are instance of class ALAsset, and we need to know, which files are panoramic images.
We have tried to get CGImageRef from ALAsset instance and check width/height ratio.
ALAsset *alasset = ...
CFImageRef = alasset.thumbnail; // return square thumbnail and not suitable for me
CFImageRef = alasset.aspectRationThumbnail; //return aspect ration thumbnail, but very slowly
It isn't suitable for us, because it works slowly for many files.
Also we have tried to get metadata from defaultRepresentation and check image EXIF, but it works slowly to.
NSDictionary *dictionary = [alasset defaultRepresentation] metadata]; //very slowly to
Is there any way to make it better?
Thanks
Finaly, I've found this solution for ALAsset:
ALAssetsLibrary *assetsLibrary = ...;
NSOperation *queue = [NSoperationQueue alloc] init];
static NSString * const kAssetQueueName = ...;
static NSUInteger const kAssetConcurrentOperationCount = ...; //I use 5
queue.maxConcurrentOperationCount = kAssetConcurrentOperationCount;
queue.name = kAssetQueueName;
dispatch_async(dispatch_get_main_queue(), ^{
[assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
/*You must check the group is not nil */
if (!group)
return;
/*Then you need to select group where you will search panoramas: for iPhone-Simulator it's #"Saved Photos" and "Camera Roll" for iPhone. It's actuality only for iOS 7 or early. */
static NSString * const kAssetGroupName = ...;
if ([[group valueForProperty:ALAssetsGroupPropertyName] kAssetGroupName]) {
[group enumerateAssetsUsingBlock:^(ALAsset *asset, NSUInteger index, BOOL *stop) {
if (!asset)
return;
[queue addOperationWithBlock:^{
//I use #autoreleasepool for instant memory release, after I find panoramas asset url
#autoreleasepool {
ALAssetRepresentation *defaultRepresentation = asset.defaultRepresentation;
if ([defaultRepresentation.UTI isEqualToString:#"public.jpeg"]) {
NSDictionary *metadata = defaultRepresentation.metadata;
if (!metadata)
return;
if (metadata[#"PixelWidth"] && metadata[#"PixelHeight"]) {
NSInteger pixelWidth = [metadata[#"PixelWidth"] integerValue];
NSInteger pixelHeight = [metadata[#"PixelHeight"] integerValue];
static NSUInteger const kSidesRelationshipConstant = ...; //I use 2
static NSUInteger const kMinimalPanoramaHeight = ...; //I use 600
if (pixelHeight >= kMinimalPanoramaHeight && pixelWidth/pixelHeight >= kSidesRelationshipConstant) {/*So, that is panorama.*/}
}
}];
}];
}
} failureBlock:^(NSError *error) {
//Some failing action, you know.
}];
};
That is. So, I think that is not the best solution. However, for today I have not found any better.

Storing images in NSMutableArray using GCD

I am trying to store images from a remote location to an NSMutableArray using a GCD block. The following code is being called in viewDidLoad, and the images are to be populated in a UICollectionView:
dispatch_apply(self.count, dispatch_get_global_queue(0, 0), ^(size_t i){
NSString *strURL = [NSString stringWithFormat:#"%#%zu%#", #"http://theURL.com/popular/", i, #".jpg"];
NSURL *imageURL = [NSURL URLWithString:strURL];
NSData *imageData = [NSData dataWithContentsOfURL: imageURL];
UIImage *oneImage =[UIImage imageWithData:imageData];
if(oneImage!=nil){
[self.imageArray addObject:oneImage];
}
});
PROBLEM: the images are not being linearly stored.
eg.
[self.imageArray objectAtIndex:2] is not 2.jpg
Even though it is setting first and last image correct, rest are all jumbled up.
Another way to do this (What I basically need, minus the time consumed and memory overhead):
for (int i=0; i<=[TMAPopularImageManager sharedInstance].numberOfImages-1; i++){
NSString *strURL = [NSString stringWithFormat:#"%#%d%#", #"http://theURL.com/popular/", i, #".jpg"];
NSURL *imageURL = [NSURL URLWithString:strURL];
NSData *imageData = [NSData dataWithContentsOfURL: imageURL];
UIImage *oneImage =[UIImage imageWithData:imageData];
if(oneImage!=nil){
[self.imageArray addObject:oneImage];
}
}
Is there a better way to implement the GCD block in this case? I need the images in the array sequentially named.
I ran this about 10 times and it did not print in order once:
NSInteger iterations = 10;
dispatch_apply(iterations, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long) NULL), ^(size_t index) {
NSLog(#"%zu", index);
});
My suggestion, and what I have done in the past, is to just run a for loop inside the block of a background thread:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long) NULL);
dispatch_async(queue, ^{
int iterations = 0;
for (int x = 0; x < iterations; ++x) {
// Do stuff here
}
dispatch_async(dispatch_get_main_queue(), ^{
// Set content generated on background thread to main thread
});
});
When using background threads, it is important to make sure that either the the objects you initialized on the main thread are thread-safe or that you initialize objects in the background and then set the main thread's objects with the background-thread-created objects like in the above example. This is especially true with Core Data.
EDIT:
Seems like the iterations using dispatch_apply return immediately so they will probably execute out of order when doing anything meaningful. If you run these two, you will see that printf always runs in order but NSLog does not:
NSInteger iterations = 10;
dispatch_apply(iterations, the_queue, ^(size_t idx) {
printf("%zu\n", idx);
});
dispatch_apply(iterations, the_queue, ^(size_t idx) {
NSLog(#"%zu\n", idx);
});
In my opinion, it would be best to run a standard for statement in a background thread rather than dispatch_apply if the order is important.
EDIT 2:
This would be your implementation:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long) NULL);
dispatch_async(queue, ^{
int iterations = 0;
NSMutableArray *backgroundThreadImages = [NSMutableArray array];
for (int i = 0; i < iterations; ++i) {
NSString *strURL = [NSString stringWithFormat:#"%#%i%#", #"http://theURL.com/popular/", i, #".jpg"];
NSURL *imageURL = [NSURL URLWithString:strURL];
NSData *imageData = [NSData dataWithContentsOfURL: imageURL];
UIImage *oneImage =[UIImage imageWithData:imageData];
if(oneImage!=nil){
[backgroundThreadImages addObject:oneImage];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
self.imageArray = backgroundThreadImages;
});
});
Not sure if applicable to your situation, but if the images are going to be displayed in a UICollectionView, it is better to load them when required. That is, when the delegate methods (cellFor...) are invoked.
That way you only load what is needed, when it's needed.
If you need to have the images in an array for some other purpose, ignore this answer.
two ways: sort the array afterwards
or
// assume self.imageArray is empty
for (int i = 0; i < self.count; ++i) // fill array with NSNull object
[self.imageArray addObject:[NSNull null]]];
dispatch_apply(self.count, dispatch_get_global_queue(0, 0), ^(size_t i){
NSString *strURL = [NSString stringWithFormat:#"%#%zu%#", #"http://theURL.com/popular/", i, #".jpg"];
NSURL *imageURL = [NSURL URLWithString:strURL];
NSData *imageData = [NSData dataWithContentsOfURL: imageURL];
UIImage *oneImage =[UIImage imageWithData:imageData];
if(oneImage!=nil){
self.imageArray[i] = oneImage;
}
});
I am not sure it is thread-safe to operate NSMutableArray this way, you may need
#synchronized(self.imageArray) {
self.imageArray[i] = oneImage;
}

Resources