Race Condition (?) In iPhone Temp File Writing - iphone

I'm creating some temporary files in the iPad simulator. To test my file creation, I create the file and then read it back. Here's some code to show this:
-(NSString *) writeToTempFile:(UIImage*) image{
NSString *path = [self createTemporaryFile];
NSLog(#"path: %#", path);
NSData *data = UIImageJPEGRepresentation(image, 1);
[data writeToFile:path atomically:YES];
free(data);
return path;
}
-(UIImage *) readTempFile:(NSString *) path{
NSData *data = [[NSData alloc] initWithContentsOfFile:path];
UIImage *image = [[UIImage alloc] initWithData:data];
return image;
}
I call these methods one after another, before a final function writes out the UIImage to the photo album.
UIImageWriteToSavedPhotosAlbum(image2, self, nil, nil);
The problem is, this always crashes my app on the third time it is executed. First and second time it successfully does all of this and stores to the album. Third time it crashes to Home. Any ideas?

NSData *data = UIImageJPEGRepresentation(image, 1);
[data writeToFile:path atomically:YES];
free(data);
The NSData returned from UIImageJPEGRepresentation is -autoreleased. There is no need to free() it. And it is wrong to free() any Objective-C objects — send a -release message instead.
Please read through the Memory Management Programming Guide.

Related

Storing and retrieving images on iPhone efficiently

I have an application which donwloads several images and stores them on the phone. In total it will probably required around 20 images tops. I need to be able to retrieve any of these images at will depending on what screen the user is on. These images will be stored indefinitely, so I don't want to use temp directory.
At present I have a class named Images with these methods
- (void) cacheImage: (NSString *) ImageURLString : (NSString *)imageName
{
NSURL *ImageURL = [NSURL URLWithString: ImageURLString];
// Generate a unique path to a resource representing the image you want
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [paths objectAtIndex: 0];
NSString *docFile = [docDir stringByAppendingPathComponent: imageName];
// Check for file existence
if(![[NSFileManager defaultManager] fileExistsAtPath: docFile])
{
// The file doesn't exist, we should get a copy of it
// Fetch image
NSData *data = [[NSData alloc] initWithContentsOfURL: ImageURL];
UIImage *image = [[UIImage alloc] initWithData: data];
// Is it PNG or JPG/JPEG?
// Running the image representation function writes the data from the image to a file
if([ImageURLString rangeOfString: #".png" options: NSCaseInsensitiveSearch].location != NSNotFound)
{
[UIImagePNGRepresentation(image) writeToFile: docFile atomically: YES];
}
else if([ImageURLString rangeOfString: #".jpg" options: NSCaseInsensitiveSearch].location != NSNotFound ||
[ImageURLString rangeOfString: #".jpeg" options: NSCaseInsensitiveSearch].location != NSNotFound)
{
[UIImageJPEGRepresentation(image, 100) writeToFile: docFile atomically: YES];
}
}
}
- (UIImage *) getCachedImage : (NSString *)imageName
{
NSArray *paths = NSSearchPathForDirectoriesInDomains
(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* cachedPath = [documentsDirectory stringByAppendingPathComponent:imageName];
UIImage *image;
// Check for a cached version
if([[NSFileManager defaultManager] fileExistsAtPath: cachedPath])
{
image = [UIImage imageWithContentsOfFile: cachedPath]; // this is the cached image
}
else
{
NSLog(#"Error getting image %#", imageName);
}
return image;
}
-(void)getImages
{
//example
NSString *image1URL = #"http://test/image1.png";
NSString *image2URL = #"http://test/image2.png";
NSString *image3URL = #"http://test/image3.png";
[self cacheImage:sLogo: #"Image1"];
[self cacheImage:sBlankNav: #"Image2"];
[self cacheImage:buttonLarge :#"Image3"];
}
-(void) storeImages
{
image1 = [self getCachedImage:#"Image1"];
image2 = [self getCachedImage:#"Image2"];
image3 = [self getCachedImage:#"Image3"];
}
So I use the code like this
Images *cache = [[Images alloc]init];
[cache storeImages];
The get images method is called once when the app first starts to get the images, it isn't called again after that, unless the images on the server are updated and I need to retrieve the updated ones.
The code works, but the problem is when I navigate to a screen that uses it, there is a very slight delay before the screen loads as it is loading the images.
My application is a tabbed application, so it begins on tab 1, I click tab 2 which implements the code, there will be a slight pause the first time it loads. It doesn't last very long, but it is noticeable and is very annoying. After that it is fine, as it is already loaded. However with navigation controller, every time you move from the first VC to the second VC, the method will be called again, so each time you navigate the delay will be there.
The images are not very big, biggest one is 68kb, others are much smaller than that. At present I am just testing with 5 images. Is there a more efficient way of storing and retrieving images, or am I doing something wrong with my code? I need to be able to retrieve these images without any noticeable delay in order for my application to remain fluid and not jerky or clunky.
Thanks in advance!!
You have two options to do the image loading work on a background thread - use Grand Central Dispatch or NSInvocationOperation. GCD might be considered the cleaner of the two:
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_async(q, ^{
//load images here
dispatch_async(main, ^{
// show on main thread here
});
});
you have delay because you're downloading data synchronously
// NSData *data = [[NSData alloc] initWithContentsOfURL: ImageURL];
Try some smart library like SDWebImage:
it lets you download image asynchronously while you still can display a local image (a proxy image). By the way, you still get cache image for free. So even if u are on local, you can still catch previously downloaded images
https://github.com/rs/SDWebImage
A must have

Corrupted Images in document directory

I am fetching URL of the images from the server and converting this images into png format using:
NSData *data1 = [NSData dataWithData:UIImagePNGRepresentation(img)];
[data1 writeToFile:pngFilePath atomically:YES];
but some of the images are corrupted when i check them after completing the process on simulator.
Hence these images are not displaying on the app wherever needed.
Please see the attached image as some images are corrupted.
Update
I am calling a method in a loop which fetches the images from the server parallel and didFinishLoading I am performing this:
UIImage *img = [UIImage imageWithData:self.data];
NSArray *split = [self.strImageName componentsSeparatedByString:#"/"];
int arrCount=[split count];
NSString *imageName=[split objectAtIndex:arrCount-1];
NSString *docDirec = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *pngFilePath=nil
pngFilePath = [NSString stringWithFormat:#"%#/Thumbs/%#",docDirec,imageName];
NSData *data1 = [NSData dataWithData:UIImagePNGRepresentation(img)];
[data1 writeToFile:pngFilePath atomically:YES];
[self.data release]; //don't need this any more, its in the UIImageView now
self.data=nil;
i have the similar problem
i solved it using the below code
- (void)viewWillDisappear:(BOOL)animated
{
if (self.m_objWebManager != nil)//Webmanager is class for downloading the images as a background thread
{
[self.m_objWebManager cancelCommunication];
[self.m_objWebManager release];
self.m_objWebManager = nil;
}
}

iPhone image save from web issues

I have code that will check to see if an image is on the phone or not (the name being retrieved from the db), and if not, it runs this code:
NSString *urlString = [NSString stringWithFormat: #"http://www.vegashipster.com/%#",image_path];
NSData *imageData = [[NSData alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]]];
NSLog(#"saving jpg");
UIImage *image = [[UIImage alloc] initWithData:imageData];//1.0f = 100% quality
[UIImageJPEGRepresentation(image, 1.0f) writeToFile:myFilePath atomically:YES];
NSLog(#"saving image done");
NSLog(#"URL String: %#",urlString);
NSLog(#"http://www.vegashipster.com/%#",image_path);
rest_image = [UIImage imageNamed:image_path];
[image release];
[imageData release];
This code works just fine on the simulator, but on the iPhone, the screen freezes for a few seconds (downloading the image, I think), then loads the page, but with no image visible. The next time you hit that page, there is no freeze. So I believe the file is being created, but it's a messed up image file, and therefore not displayed.
I've already broken this code up so that it runs in it's own thread, and again, it works in the simulator. I had thought that if it ran behind the scenes, there would be less of a chance that the image data would get messed up, but the exact same thing happens (minus the freezing). Does anyone know what I am doing wrong with this code? Thanks for any and all help/comments.
Edit
And yes, the images being downloaded are strictly .jpg
Edit 2
I seen:
Make sure you are writing to your DOCUMENTS directory, which you have read+write access to. Otherwise you won't get any files.
at http://www.iphonedevsdk.com/forum/iphone-sdk-development/15628-file-weirdness-files-written-disk-do-not-appear-nsfilemanager-defaultmanager.html . Could this be my issue?
NSString *image_path = [[NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 5)] stringByReplacingOccurrencesOfString:#"../"
NSString *myFilePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:image_path];
Last Edit
Well, I found what I believe to be my answer at How can I get a writable path on the iPhone? . It pretty much states I cannot save image files where my own image files are located inside the build. If this is incorrect, please let me know and I will try your way.
NSData *imageData = [[NSData alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:urlString]]];
Is a synchronous call, ie it blocks until it has fully executed, which in the case of network operations can be 1 second, 10 seconds or 3 minutes if you don't have a time out. You are presumably running this on the main thread which is why your UI freezes (all UI stuff is done on the main thread so you must do everything not to block it). The reason it doesn't freeze the next time around is probably that it has cached the image data.
You should use asynchronous APIs, NSURLConnection has some, however I strongly recommend ASIHTTPRequest, which is an obj c wrapper around NSURLConnection and co. The code would look something like this (read through the how to use section)
- (IBAction)grabURLInBackground:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
UIImage* downloadedImage = [UIImage imageWithData:responseData];
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
Sorry, forgot about this thread completely. I did end up fixing this issue. Here is what I did:
NSFileManager* fileManager = [NSFileManager defaultManager];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *libraryDirectory = [paths objectAtIndex:0];
NSString *myFilePath = [libraryDirectory stringByAppendingPathComponent:image_path];
BOOL fileExists = [fileManager fileExistsAtPath:myFilePath];
if (fileExists){
hotel_image = [UIImage imageWithContentsOfFile:myFilePath];
}else{
[NSThread detachNewThreadSelector:#selector(downloadImage:) toTarget:[HotelsDetailsViewController class] withObject:image_path];
fileExists = [fileManager fileExistsAtPath:myFilePath];
if (fileExists){
hotel_image = [UIImage imageWithContentsOfFile:myFilePath];
}else{
hotel_image = [UIImage imageNamed:#"hdrHotels.jpg"];
}
}
In short, I found my app's library directory and saved there.

Objective C code to handle large amount of data processing in iPhone

I had the following code that takes in 14 mb or more of image data encoded in base4 string and converts them to jpeg before writing to a file in iphone. It crashes my program giving the following error :
Program received signal: “0”.
warning: check_safe_call: could not restore current frame
I tweak my program and it can process a few more images before the error appear again. My coding is as follows:
// parameters is an array where the fourth element contains a list of images in base64 >encoded string
NSMutableArray *imageStrList = (NSMutableArray*) [parameters objectAtIndex:5];
while (imageStrList.count != 0) {
NSString *imgString = [imageStrList objectAtIndex:0];
// Create a file name using my own Utility class
NSString *fileName = [Utility generateFileNName];
NSData *restoredImg = [NSData decodeWebSafeBase64ForString:imgString];
UIImage *img = [UIImage imageWithData: restoredImg];
NSData *imgJPEG = UIImageJPEGRepresentation(img, 0.4f);
[imgJPEG writeToFile:fileName atomically:YES];
[imageStrList removeObjectAtIndex:0];
}
I tried playing around with UIImageJPEGRepresentation and found out that the lower the value, the more image it can processed but this should not be the way. I am wondering if there is anyway to free up memory of the imageStrList immediately after processing each image so that it can be used by the next one in the line.
Your problem is that the UIImages and NSDatas are piling up in memory in a loop. Normally the autorelease pool would take care of this at the end of the event loop, but you're not letting it get that far. You have two choices:
Explicitly allocate/init and then release when done the UIImage and NSData objects, rather than using the +class methods.
Create an explicit autorelease pool inside the loop, and drain it at the end. Do this with:
NSAutoreleasePool * myPool = [[NSAutoreleasePool alloc] init];
[myPool release];
NSMutableArray *imageStrList = (NSMutableArray*) [parameters objectAtIndex:5];
while (imageStrList.count != 0) {
NSAutoReleasePool *pool = [[NSAutoReleasePool alloc] init];
NSString *imgString = [imageStrList objectAtIndex:0];
// Create a file name using my own Utility class
NSString *fileName = [Utility generateFileNName];
NSData *restoredImg = [NSData decodeWebSafeBase64ForString:imgString];
UIImage *img = [UIImage imageWithData: restoredImg];
NSData *imgJPEG = UIImageJPEGRepresentation(img, 0.4f);
[imgJPEG writeToFile:fileName atomically:YES];
[imageStrList removeObjectAtIndex:0];
[pool release];
}
You should create another autoreleasepool inside the loop because objects created inside loop won't be released until the program get out from the loop.
Auto release pool inside the loop will release all objects created inside the loop every time.

To add UIImage directly to file

I want to add my UIImage directly into the file instead of converting into UIImagePNGRepresentation or UIImageJPGRepresentation(as it takes time) like:-
UIImage *im = [UIImage imageWithCGImage:ref];
[array addObject:im];
NSData *data = [array objectAtIndex:i];
[data writeToFile:path atomically:YES];
But it is showing error.
So there is any way that i can do it.
Thanks in Advance.
your use of the array only obfuscates that you are basically doing:
NSData *data = im;
Which cannot possibly work because im is a UIImage, and not an NSData nor a subclass.
What you want to do is to create a new NSData and initialize it with the content of the image. Since you got a CGImageRef, I suggest using it directly, without using a UIImage in between.
CGDataProviderRef imageDataProvider = CGImageGetDataProvider(ref);
CFDataRef imageData = CGDataProviderCopyData(imageDataProvider);
NSData *data = (NSData*) imageData;
Note that it is OK to cast the CFDataRef to NSData* because CFData is “toll-free bridged” with its Cocoa Foundation counterpart, NSData.
I hope that helps.
(don't forget to release data when done)