I'm writing an application that will take several images from URL's, turn them into a UIImage and then add them to the photo library and then to the custom album. I don't believe its possible to add them to a custom album without having them in the Camera Roll, so I'm accepting it as impossible (but it would be ideal if this is possible).
My problem is that I'm using the code from this site and it does work, but once it's dealing with larger photos it returns a few as 'Write Busy'. I have successfully got them all to save if I copy the function inside its own completion code and then again inside the next one and so on until 6 (the most I saw it take was 3-4 but I don't know the size of the images and I could get some really big ones) - this has lead to the problem that they weren't all included in the custom album as they error'd at this stage too and there was no block in place to get it to repeat.
I understand that the actual image saving is moved to a background thread (although I don't specifically set this) as my code returns as all done before errors start appearing, but ideally I need to queue up images to be saved on a single background thread so they happen synchronously but do not freeze the UI.
My code looks like this:
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:singleImage]]];
[self.library saveImage:image toAlbum:#"Test Album" withCompletionBlock:^(NSError *error) {
if (error!=nil) {
NSLog(#"Error");
}
}];
I've removed the repetition of the code otherwise the code sample would be very long! It was previously where the NSLog code existed.
For my test sample I am dealing with 25 images, but this could easily be 200 or so, and could be very high resolution, so I need something that's able to reliably do this over and over again without missing several images.
thanks
Rob
I've managed to make it work by stripping out the save image code and moving it into its own function which calls itself recursively on an array on objects, if it fails it re-parses the same image back into the function until it works successfully and will display 'Done' when complete. Because I'm using the completedBlock: from the function to complete the loop, its only running one file save per run.
This is the code I used recursively:
- (void)saveImage {
if(self.thisImage)
{
[self.library saveImage:self.thisImage toAlbum:#"Test Album" withCompletionBlock:^(NSError *error) {
if (error!=nil) {
[self saveImage];
}
else
{
[self.imageData removeObject:self.singleImageData];
NSLog(#"Success!");
self.singleImageData = [self.imageData lastObject];
self.thisImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self.singleImageData]]];
[self saveImage];
}
}];
}
else
{
self.singleImageData = nil;
self.thisImage = nil;
self.imageData = nil;
self.images = nil;
NSLog(#"Done!");
}
}
To set this up, I originally used an array of UIImages's but this used a lot of memory and was very slow (I was testing up to 400 photos). I found a much better way to do it was to store an NSMutableArray of URL's as NSString's and then perform the NSData GET within the function.
The following code is what sets up the NSMutableArray with data and then calls the function. It also sets the first UIImage into memory and stores it under self.thisImage:
NSEnumerator *e = [allDataArray objectEnumerator];
NSDictionary *object;
while (object = [e nextObject]) {
NSArray *imagesArray = [object objectForKey:#"images"];
NSString *singleImage = [[imagesArray objectAtIndex:0] objectForKey:#"source"];
[self.imageData addObject:singleImage];
}
self.singleImageData = [self.imageData lastObject];
self.thisImage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self.singleImageData]]];
[self saveImage];
This means the rest of the getters for UIImage can be contained in the function and the single instance of UIImage can be monitored. I also log the raw URL into self.singleImageData so that I can remove the correct elements from the array to stop duplication.
These are the variables I used:
self.images = [[NSMutableArray alloc] init];
self.thisImage = [[UIImage alloc] init];
self.imageData = [[NSMutableArray alloc] init];
self.singleImageData = [[NSString alloc] init];
This answer should work for anyone using http://www.touch-code-magazine.com/ios5-saving-photos-in-custom-photo-album-category-for-download/ for iOS 6 (tested on iOS 6.1) and should result in all pictures being saved correctly and without errors.
If saveImage:toAlbum:withCompletionBlock it's using dispatch_async i fear that for i/o operations too many threads are spawned: each write task you trigger is blocked by the previous one (bacause is still doing I/O on the same queue), so gcd will create a new thread (usually dispatch_async on the global_queue is optimized by gcd by using an optimized number of threads).
You should either use semaphores to limit the write operation to a fixed number at the same time or use dispatch_io_ functions that are available from iOS 5 if i'm not mistaken.
There are plenty example on how to do this with both methods.
some on the fly code for giving an idea:
dispatch_semaphore_t aSemaphore = dispatch_semaphore_create(4);
dispatch_queue_t ioQueue = dispatch_queue_create("com.customqueue", NULL);
// dispatch the following block to the ioQueue
// ( for loop with all images )
dispatch_semaphore_wait(aSemaphore , DISPATCH_TIME_FOREVER);
[self.library saveImage:image
toAlbum:#"Test Album"
withCompletionBlock:^(NSError *error){
dispatch_semaphore_signal(aSemaphore);
}];
so every time you will have maximum 4 saveImage:toAlbum, as soon as one completes another one will start.
you have to create a custom queue, like above (the ioQueue) where to dispatch the code that does the for loop on the images, so when the semaphore is waiting the main thread is not blocked.
Related
I noticed that the more I use GPUImage, the more memory my app takes over time (using Instruments to monitor memory use).
As an example, I use each filter in a different methods:
(UIImage*)ToonFilter:(UIImage*)theImage
{
GPUImageSmoothToonFilter *smoothToonFilter = [[GPUImageSmoothToonFilter alloc] init];
[smoothToonFilter setTexelHeight:0.0025];
[smoothToonFilter setTexelWidth:0.0025];
return [smoothToonFilter imageByFilteringImage:theImage];
}
(UIImage*)SketchFilter:(UIImage*)theImage
{
GPUImageSketchFilter *sketchFilter = [[GPUImageSketchFilter alloc] init];
[sketchFilter setTexelHeight:0.003];
[sketchFilter setTexelWidth:0.003];
return [sketchFilter imageByFilteringImage:theImage];
}
(UIImage*)PixellateFilter:(UIImage*)theImage
{
GPUImagePixellateFilter *pixellateFilter = [[GPUImagePixellateFilter alloc] init];
[pixellateFilter setFractionalWidthOfAPixel:0.01;
return [pixellateFilter imageByFilteringImage:theImage];
}
And this is how I use these filters (testImage is a UIImage):
testImage = [self SketchFilter:testImage];
testImage = [self PixellateFilter:testImage];
If I just cycle through these filters over and over, without doing anything else, the app takes more and more memory.
What am I doing wrong? How can I release the memory once I don't need it anymore?
You keep allocating new filters every time you call these functions. Create them once in your view controller etc and send them to your functions like the sample code below (as well as a GPUImagePicture). Also don't forget to call [removeAllTargets] on your filters, sometimes they do not get removed from memory when they have targets
(UIImage*)PixellateFilter:(UIImage*)theImage withPixellateFilter:(GPUImagePixellateFilter *) pixellateFilter andStaticPicture:(GPUImagePicture *)staticPicture
and also use [filter prepareForImageCapture] before getting UIImage from your filters
For example you can build your function like
(UIImage*)PixellateFilter:(UIImage*)theImage withPixellateFilter:(GPUImagePixellateFilter *) pixellateFilter andStaticPicture:(GPUImagePicture *)staticPicture{
[staticPicture removeAllTargets];
UIImage __block *imageToReturn;
[staticPicture addTarget:pixellateFilter];
[staticPicture processImageWithCompletionHandler:^{
[pixellateFilter prepareForImageCapture];
imageToReturn = [pixellateFilter imageFromCurrentlyProcessedOutput];
[pixellateFilter removeAllTargets];
pixellateFilter = nil;
}];
return imageToReturn;
}
I use GPUImageView for my application which allows you to use GPUImageFilters and GPUImagePictures directly without getting UIImage, you should definitely consider it
Update: This problem was also reported here, with a more detailed treatment of the cause:
UIImageWriteToSavedPhotosAlbum saves only 5 image out of 10. Why?
In my case as well, the error was: "Write busy" - this seems to be an issue related to device speed. There is probably some solution that involves manually handling threading or similar - but, inspired by Tommy's answer below, I serialized the saving of images, and that works around the problem.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Situation:
I'm trying to save a large-ish collection of images to the camera roll using a for-loop (number of images depends on user input - may be 1, 2, 3, or theoretically hundreds). Images are pointed to by an NSSet (though I could just as easily do an NSArray), and the NSSet is set to nil after the for-loop completes.
I'm using UIImageWriteToSavedPhotosAlbum() to save out the images on a detached thread (using NSThread's detachNewThreadSelector:toTarget:withObject: class method), and starting a UIActivityIndicator spinner on the main thread.
Problem:
When I attempt to save out more than ~5, any image after the ~5th will output this error in the log:
*** -[NSKeyedUnarchiver initForReadingWithData:]: data is NULL
For example, let's say I exported 9 images. The for-loop will run through all 9 images (confirmed by NSLogging), but I'll get around 4 iterations of the error above (and only 5 images saved to the camera roll).
If I add a breakpoint in the loop and wait a second or two in between each iteration, however, they are all saved correctly without complaint. So..
Theory:
Based on my logs and observations, UIImageWriteToSavedPhotosAlbum() is clearly running asynchronously and is somehow 'too slow' to keep up with my application.
Is there a simple way to force it to run synchronously (ideally on the main thread)? I've been experimenting with adding reference counts to the images I'm trying to save out, but (1) this feels hacky, and (2) I haven't solved the problem regardless.
Any advice would be great. Thanks!
I use ALAssetsLibrary and a dispatch_queue to make sure that images are saved sequentially:
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
dispatch_queue_t queue = dispatch_queue_create("com.myApp.saveToCameraRoll", NULL);
[images enumerateObjectsUsingBlock:^(UIImage *image, NSUInteger idx, BOOL *stop) {
dispatch_async(queue, ^{
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[library writeImageToSavedPhotosAlbum:image.CGImage metadata:metaData completionBlock:^(NSURL *assetURL, NSError *writeError) {
if (writeError) {
// handle the error
}
else {
if (image == [images lastObject]) {
dispatch_async(dispatch_get_main_queue(), ^{
// perhaps indicate to the user that save has finished
});
}
}
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
});
}];
If you're desperate not to write multiple images at once then you could use UIImageWriteToSavedPhotosAlbum's completion target and selector to implement a form of tail recursion. Something like:
- (void)writeSetToSavedPhotosAlbum:(NSMutableSet *)images
{
if(![images count]) return;
UIImage *imageToSave = [[[images anyObject] retain] autorelease];
[images removeObject:imageToSave];
NSLog(#"I shall now write image %#", imageToSave);
UIImageWriteToSavedPhotosAlbum(
imageToSave,
self,
#selector(writeSetToSavedPhotosAlbum:),
images);
}
EDIT: it may also be worth seeing whether you get the same results with ALAssetsLibrary's -writeImageToSavedPhotosAlbum:orientation:completionBlock:, which takes a block for completion so is even more straightforward to work with.
The docs do mention you'll be notified asynchronously. I wonder if this might work: for each image you want to save create a block operation and add it to main operation queue. Kind of like:
for (UIImage *toSave in imagesToSave)
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UIImageWriteToSavedPhotosAlbum(toSave, target, selector, ctx);
}];
}
Might be worth a shot, let me know if it helps!
In this Code Work Save image :
UIImageWriteToSavedPhotosAlbum([self screenshot], nil, nil, nil);
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Save" message:#"Photo saved to album" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alert show];
My goal is to make a video out of a short sequence of opengl frames (around 200 frames). So in order to do this, I use the following code to create a array of images:
NSMutableArray* images = [NSMutableArray array];
KTEngine* engine = [KTEngine sharedInstance]; //Opengl - based engine
for (unsigned int i = engine.animationContext.unitStart; i < engine.animationContext.unitEnd ; ++i)
{
NSLog(#"Render Image %d", i);
[engine.animationContext update:i];
[self.view setNeedsDisplay];
[images addObject:[view snapshot]];
}
NSLog(#"Total image rendered %d", [images count]);
[self createVideoFileFromArray:images];
So this works perfectly fine on simulator, but not on device (retina iPad). So my guess is that the device does not support so many UIimages (specially in 2048*1536). The crash always happends after 38 frames or so.
Now as for the solution, I thought to create a video for each 10 frames, and then attached them all together, but when can I know if I have enough space (is the autorelease pool drained?).
Maybe I should use a thread, process 10 images, and fire it again for the next 10 frames once it's over?
Any idea?
It seems quite likely that you're running out of memory.
To reduce memory usage, you could try to store the images as NSData using the PNG or JPG format instead. Both PNG's and JPG's are quite small when represented as data, but loading them into UIImage objects can be very memory consuming.
I would advice you to do something like below in your loop. The autorelease pool is needed to drain the returned snapshot on each iteration.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *image = [view snapshot];
NSData *imageData = UIImagePNGRepresentation(image);
[images addObject:imageData];
[pool release];
This of course requires your createVideoFileFromArray: method to handle pure image data instead of UIImage objects, but that should probably be feasible to implement.
I have a UITableView which displays images. Every cell has an image and every time a cell loads, I call a selector (from the cellForRowAtIndexPath) in the background like this:
[self performSelectorInBackground:#selector(lazyLoad:) withObject:aArrayOfData];
The only problem is that sometimes I get a crash (because I am changing data in the background while it's trying to be read elsewhere). Here's the error:
*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <CALayerArray: 0xce1a920> was mutated while being enumerated.'
When updating the data in the background, should I move it to the main selector and change it? Or should I call the #selector() differently?
Thanks!
If you can leave the operation on the main thread and have no lagginess nor problems you are done.
However: Let's assume you've already done that and encounter problems. The answer is: don't modify the array in the lazy load. Switch to the main thread to modify the array. See Brad's answer here:
https://stackoverflow.com/a/8186206/8047
for a way to do it with blocks, so you can send your objects over to the main queue (you should probably also use GCD for the call to the lazy load in the first place, but it's not necessary).
You can use #synchronized blocks to keep the threads from walking over each other. If you do
#synchronized(array)
{
id item = [array objectAtIndex:row];
}
in the main thread and
#synchronized(array)
{
[array addObject:item];
}
in the background, you're guaranteed they won't happen at the same time. (Hopefully you can extrapolate from that to your code—I'm not sure what all you're doing with the array there..)
It seems, though, like you'd have to notify the main thread anyway that you've loaded the data for a cell (via performSelectorOnMainThread:withObject:waitUntilDone:, say), so why not pass the data along, too?
Given the term 'lazy load' I am assuming that means you are pulling your images down from a server. (If the images are local then there is really no need for multithreading).
If you are downloading images off a server I would suggest using something along these lines (using ASIHTTPRequest)
static NSCache *cellCache; //Create a Static cache
if (!cellCache)//If the cache is not initialized initialize it
{
cellCache = [[NSCache alloc] init];
}
NSString *key = imageURL;
//Look in the cache for image matching this url
NSData *imageData = [cellCache objectForKey:key];
if (!imageData)
{
//Set a default image while it's loading
cell.icon.image = [UIImage imageNamed:#"defaultImage.png"];'
//Create an async request to the server to get the image
__unsafe_unretained ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:imageURL]];
//This code will run when the request finishes
[request setCompletionBlock:^{
//Put downloaded image into the cache
[cellCache setObject:[request responseData] forKey:key];
//Display image
cell.icon.image = [UIImage imageWithData:[request responseData]];
}];
[request startAsynchronous];
}
else
{
//Image was found in the cache no need to redownload
cell.icon.image = [UIImage imageWithData:imageData];
}
I am implementing an iOS App that needs to fetch a huge amount of images over HTTP. I've tried several approaches but independently what I do, Instuments shows constantly increasing memory allocations and the App crashes sooner or later when I run it on a device. There are no leaks shown by Instruments.
So far I have tried the following approches:
Fetch the images using a synchronous NSURLConnection within an NSOperation
Fetch the images using a asynchronous NSURLConnection within an NSOperation
Fetch the images using [NSData dataWithContentsOfURL:url] in the Main-Thread
Fetch the images using synchronous ASIHTTPRequest within an NSOperation
Fetch the images using asynchronous ASIHTTPRequest and adding it to a NSOperationQueue
Fetch the images using asynchronous ASIHTTPRequest and using a completionBlock
The Call Tree in Instrumetns shows that the memory is consumed while processing the HTTP-Response. In case of asynchronous NSURLConnection this is in
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[receivedData appendData:data];
}
In case of the synchronous NSURLConnection, Instruments shows a growing CFData (store) entry.
The problem with ASIHTTPRequest seems to be the same as with the asynchronous NSURLConnection in a analogous code-position. The [NSData dataWithContentsOfURL:url] approach shows an increasing amount of total memory allocation in exactely that statement.
I am using an NSAutoReleasePool when the request is done in a separate thread and I have tried to free up memory with [[NSURLCache sharedURLCache] removeAllCachedResponses] - no success.
Any ideas/hints to solve the problem? Thanks.
Edit:
The behaviour only shows up if I persist the images using CoreData. Here is the code I run as a NSInvocationOperation:
-(void) _fetchAndSave:(NSString*) imageId {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *url = [NSString stringWithFormat:#"%#%#", kImageUrl, imageId];
HTTPResponse *response = [SimpleHTTPClient GET:url headerOrNil:nil];
NSData *data = [response payload];
if(data && [data length] > 0) {
UIImage *thumbnailImage = [UIImage imageWithData:data];
NSData *thumbnailData = UIImageJPEGRepresentation([thumbnailImage scaleToSize:CGSizeMake(55, 53)], 0.5); // UIImagePNGRepresentation(thumbnail);
[self performSelectorOnMainThread:#selector(_save:) withObject:[NSArray arrayWithObjects:imageId, data, thumbnailData, nil] waitUntilDone:NO];
}
[pool release];
}
All CoreData related stuff is done in the Main-Thread here, so there should not be any CoreData multithreading issue. However, if I persist the images, Instruments shows constantely increasing memory allocations at the positions described above.
Edit II:
CoreData related code:
-(void) _save:(NSArray*)args {
NSString *imageId = [args objectAtIndex:0];
NSData *data = [args objectAtIndex:1];
NSData *thumbnailData = [args objectAtIndex:2];
Image *image = (Image*)[[CoreDataHelper sharedSingleton] createObject:#Image];
image.timestamp = [NSNumber numberWithDouble:[[NSDate date] timeIntervalSince1970]];
image.data = data;
Thumbnail *thumbnail = (Thumbnail*)[[CoreDataHelper sharedSingleton] createObject:#"Thumbnail"];
thumbnail.data = thumbnailData;
thumbnail.timestamp = image.timestamp;
[[CoreDataHelper sharedSingleton] save];
}
From CoreDataHelper (self.managedObjectContext is picking the NSManagedObjectContext usable in the current thread):
-(NSManagedObject *) createObject:(NSString *) entityName {
return [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:self.managedObjectContext];
}
We had a similar problem. While fetching lots of images over http, there was huge growth and a sawtooth pattern in the memory allocation. We'd see the system clean up, more or less, as it went, but slowly, and not predictably. Meanwhile the downloads were streaming in, piling up on whatever was holding onto the memory. Memory allocation would crest around 200M and then we'd die.
The problem was an NSURLCache issue. You stated that you tried [[NSURLCache sharedURLCache] removeAllCachedResponses]. We tried that, too, but then tried something a little different.
Our downloads are done in groups of N images/movies, where N was typically 50 to 500. It was important that we get all of N as an atomic operation.
Before we started our group of http downloads, we did this:
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:0];
[NSURLCache setSharedURLCache:sharedCache];
We then get each image in N over http with a synchronous call. We do this group download in an NSOperation, so we're not blocking the UI.
NSData *movieReferenceData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
Finally, after each individual image download, and after we're done with our NSData object for that image, we call:
[sharedCache removeAllCachedResponses];
Our memory allocation peak behavior dropped to a very comfortable handful of megabytes, and stopped growing.
In this case, you're seeing exactly what you're supposed to see. -[NSMutableData appendData:] increases the size of its internal buffer to hold the new data. Since an NSMutableData is always located in memory, this causes a corresponding increase in memory usage. What were you expecting?
If the ultimate destination for these images is on disk, try using an NSOutputStream instead of NSMutableData. If you then want to display the image, you can create a UIImage pointing to the file when you're done.