iOS app out of memory handling - ios

In an iOS application, I'm generating a 'voice recorder' functionality for continuous capturing of speech.
I use the following code for writing the speech in to a file.
//output speech
NSString *filePath = [root stringByAppendingPathComponent:#"output_speech.raw"];
if(![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
[[NSData data] writeToFile:filePath atomically:YES];
}
NSData *myData = [NSData dataWithBytes:ptrOut length:DataByteSize];
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:filePath];
[handle truncateFileAtOffset:[handle seekToEndOfFile]];
[handle writeData:myData];
[handle closeFile];
My question is, in case the iOS device is going out of memory, how to handle the file writing situation?

There is a very similar question here:
iPhone: available disk space
I would check for the available disk space and appropriately determine whether to cancel the write and alert the user, or to just silently fail.

#Infinity James, I obtained the free space using the method mentioned in following link. [link] (http://www.linkedin.com/groups/How-detect-total-available-free-72283.S.217861544)

Related

Restrict file Size when using freopen()

In my project, I am redirecting the NSlog Data to file by using code
freopen([FilePath cStringUsingEncoding:NSUTF8StringEncoding],"a+",stderr);
We got a issue that if we keep on writing the data file size may be increased hugely, We want to restrict the data. Lets say after the file size reached 2MB i want to clear the old data and write the new data. How can we do it, How can we check the file size in run time.
For this you can use the attributesOfItemAtPath: method of NSFileManager
NSError *err;
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:&err];
NSNumber *fileSize = [fileAttributes objectForKey:NSFileSize];
if(//Check whether the size is above 2 MB)
{
//Remove old content by either removing file or clearing it
[[NSFileManager defaultManager] removeItemAtPath:filePath error:&err];
}
else
{
//Add data
freopen([filePath cStringUsingEncoding:NSUTF8StringEncoding],"a+",stderr);
}

It is allowed to use ssziparchive library in the App Store?

Due to cryptography export regulations, it is possible to use this library? or which one could I use to compress/decompress files?
I don't know if SSZipArchive is allowed to use in distributed apps, but the library I am using is Objective-Zip.
It can be easily integrated into any project.
Sample code for zipping:
// create a zip file for writing
ZipFile *zipFile= [[ZipFile alloc] initWithFileName:pathOfTheFileToBeZipped mode:ZipFileModeCreate];
// Add a file, write to its stream and close it
ZipWriteStream *stream1= [zipFile writeFileInZipWithName:#"abc.txt" fileDate:[NSDate dateWithTimeIntervalSinceNow:-86400.0] compressionLevel:ZipCompressionLevelBest];
NSString *text= #"abc";
[stream1 writeData:[text dataUsingEncoding:NSUTF8StringEncoding]];
[stream1 finishedWriting];
// Add another file, write to its stream and close it
ZipWriteStream *stream2= [zipFile writeFileInZipWithName:#"x/y/z/xyz.txt" compressionLevel:ZipCompressionLevelNone];
NSString *text2= #"XYZ";
[stream2 writeData:[text2 dataUsingEncoding:NSUTF8StringEncoding]];
[stream2 finishedWriting];
// Close the zip file
[zipFile close];
Sample code for unzipping:
// open the zip file for reading
ZipFile *unzipFile = [[ZipFile alloc] initWithFileName:pathOfTheFileToBeUnzipped mode:ZipFileModeUnzip];
// retrieve the info of the files inside
NSArray *infos= [unzipFile listFileInZipInfos];
// iterate over files
for (FileInZipInfo *info in infos) {
// locate the file in the zip
[unzipFile locateFileInZip:info.name];
// expand the file in memory
ZipReadStream *read= [unzipFile readCurrentFileInZip];
NSData *data = [read readDataOfLength:info.length];
[read finishedReading];
// construct the folder/file path structure
NSString *unzipPathFilename = [unzipPath stringByAppendingPathComponent:info.name];
NSString *unzipPathFoldername = [[unzipPathFilename stringByDeletingLastPathComponent] copy];
NSError *errorw;
// write the unzipped files, with some consistency checks
NSRange range = [unzipPathFoldername rangeOfString:#"__MACOSX"];
if (range.location == NSNotFound) {
if ([fileManager createDirectoryAtPath:unzipPathFoldername withIntermediateDirectories:YES attributes:nil error:&errorw]) {
if (![[unzipPathFilename pathExtension] isEqualToString:#""] && ![[[unzipPathFilename lastPathComponent] substringToIndex:1] isEqualToString:#"." ]) {
[data writeToFile:unzipPathFilename atomically:NO];
}
}
else {
NSLog(#"Directory Fail: %#", errorw);
}
}
}
// close the zip file
[unzipFile close];
Actually you are allowed to have encryption in iOS application.
You just have to submit application to NSA who you are and what kind of encryption do have in app.
Respond with your reg number usually comes in 30 minutes.
It is automatic or semi-automatic.
They just collect information about developers.
It is simpler then register as iOS developer.
My opinion:-
If you do not use encryption in ZIP library then you should submit any application.
Linker will remove that code after optimization.
That code is not used. But if you use encryption even that comes with iOS then you should apply.
e.g. UIWebView if it opens https:// URLs (e.g. Facebook) but if you use UIWebView to open non secure pages then you should not apply.

Can't write large file (> 200MB, 700MB in my case) in ios device?

I am having the problem of writing large file (>200 MB) in iOS device (iPad) but in the simulator it works perfect.
I am using NSFileManager to create file and NSFileData to write file.
I think there is no problem in my code as it runs fine in the simulator.
Does anyone having the same problem?
To elaborate my situation:
I am saving chunk of files (3MB each) in my device which works fine. That means for a 300 MB file I have 100 chunks. Now, from the 100 chunks I want to create the actual file. So I am using the NSFileManager to create file in first iteration and then using NSFileData to write the 3MB data at the end of the file. While running the program it crashes after 61 chunks. I am guessing there might have some memory related issues in the iPad.
I am saving the chunk of files in fileDir in the format data-0, data-1, data-2...
I am applying the decrypt operation on data but for simplicity I have removed that portion.
// List of chunk files
NSArray *filelist= [[NSFileManager defaultManager] contentsOfDirectoryAtPath:fileDir error:err];
for(int i = 0; i < [filelist count]; i++) {
// Read the chunk of file
fileName = [[NSString alloc] initWithFormat:#"data-%d", i];
filePath = [fileDir stringByAppendingPathComponent:fileName];
fileReadHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
// Write in tempFile
if(offset == 0){
if([[NSFileManager defaultManager] createFileAtPath:tempFile contents:data attributes:nil]){
fileWriteHandle = [NSFileHandle fileHandleForWritingAtPath:tempFile];
NSLog(#"File was created!");
} else {
NSLog(#"File was not created.");
}
} else {
[fileWriteHandle seekToEndOfFile]; // Tried with comment out this line but same problem
// Write the decrypted data from chunk
[fileWriteHandle writeData:[[fileReadHandle readDataToEndOfFile] decryptedAES256DataUsingKey:AESEncryptionKey error:err]];
}
}
Edit (11.02.2013)
I tried with my previous code where I omitted the data decryption part.
Interestingly, the problem was in the decryption part I guess cause without the decryption it works fine. I have added the decryption code. For decryption I am using NSData+CommonCrypto library (it's non ARC) but my project is in ARC.
It could be an operating system issue because the NSFileHandle is never being closed for each chunk. I would recommend closing it.
Also, it looks like you have your variables declared outside the scope of the for loop. Unless you need those variables outside the loop, it's generally good to keep the scope of your variables as small as possible, especially if you are using ARC and are trying to think about when memory will be released.
If you think that the NSFileHandle is holding onto data in memory, try to use the -synchronizeFile method after writing each chunk to make sure in memory changes are reflected to disk.
Also, I moved the creation of the file you are writing to outside the loop, because it's easier to follow for me.
Try this adjustment:
// List of chunk files
NSArray *filelist= [[NSFileManager defaultManager] contentsOfDirectoryAtPath:fileDir error:err];
if([[NSFileManager defaultManager] createFileAtPath:tempFile contents:[NSData data] attributes:nil]){
NSLog(#"File was created!");
} else {
NSLog(#"File was not created.");
}
NSFileHandle *fileWriteHandle = [NSFileHandle fileHandleForWritingAtPath:tempFile];
for(int i = 0; i < [filelist count]; i++) {
// Read the chunk of file
NSString *fileName = [NSString stringWithFormat:#"data-%d", i];
NSString *filePath = [fileDir stringByAppendingPathComponent:fileName];
NSFileHandle *fileReadHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
NSData *data = [fileReadHandle readDataToEndOfFile];
// No longer using the file
[fileReadHandle closeFile];
// Write in tempFile
[fileWriteHandle writeData:data];
[fileWriteHandle synchronizeFile];// Flush any data in memory to disk
}
[fileWriteHandle closeFile];
Modifying the following code worked like a magic,
#autoreleasepool {
[fileWriteHandle writeData:[[fileReadHandle readDataToEndOfFile] decryptedAES256DataUsingKey:AESEncryptionKey error:err]];
}

Prevent an app crash due to a slow connection when retrieving a remote file

I am currently using a function in my app's didFinishLaunchingWithOptions that retrieves a file, saves it to the application directory.
I have found that when there is a weak connection the app will crash when this is happening. I read that there is a 20 second time limit Apple allows before crashing the app. Is this correct? If so, I believe this is causing my issue as the app works flawlessly with the exception of being on a very weak connection.
How could I modify my logic below to try and compensate for this?
- (void)writeJsonToFile
{
//applications Documents dirctory path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//live json data url
NSString *stringURL = #"http://link-to-my-data.json";
NSURL *url = [NSURL URLWithString:stringURL];
NSData *urlData = [NSData dataWithContentsOfURL:url];
//attempt to download live data
if (urlData)
{
NSString *filePath = [NSString stringWithFormat:#"%#/%#", documentsDirectory,#"data.json"];
[urlData writeToFile:filePath atomically:YES];
}
//copy data from initial package into the applications Documents folder
else
{
//file to write to
NSString *filePath = [NSString stringWithFormat:#"%#/%#", documentsDirectory,#"data.json"];
//file to copy from
NSString *json = [ [NSBundle mainBundle] pathForResource:#"data" ofType:#"json" inDirectory:#"html/data" ];
NSData *jsonData = [NSData dataWithContentsOfFile:json options:kNilOptions error:nil];
//write file to device
[jsonData writeToFile:filePath atomically:YES];
}
}
It's a very bad idea to run this sort of thing on the main thread: I assume you are - basically, you'll block the entire UI while you wait for the network operation to complete.
dataWithContentsOfURL is not a good idea for this sort of thing. It will be much better to use NSURLConnection or one of the wrapper libraries like AFNetworking, because you can handle cases like when the connection times out gracefully.
These libraries also have built-in methods to asynchronously download the data, which prevents the main UI thread from being locked.
When is this downloaded data needed?
Depending on the answer, maybe you can call the method inside a thread. This will prevent the main thread from blocking.
Even if the data is needed from the beginning, you can just create a loader and download the file in the background, then make the app active after the file is downloaded.
I think to be more independent from internal implementation of NSData *urlData = [NSData dataWithContentsOfURL:url]; you should implement you own download class based on NSURLConnection.
The links to read:
URL Loading System Programming Guide
NSURLConnection Class Reference
NSURLConnectionDelegate Protocol Reference
So you can catch all connection errors by your code and implement right behavior in this case.

[NSConcreteFileHandle writeData:]: Bad file descriptor - Crashes app on startup

I'm using GTMLogger and have the following piece of code that seems to be crashing since moving to iOS5.1 . The weird thing i can't seem to reproduce it, but i know its happening to many users, so I'm not sure how to track it.
[NSConcreteFileHandle writeData:]: Bad file descriptor
I could just try/catch it but its not a solution as much as a workaround.
This is the suspicious part:
NSFileHandle *file = [NSFileHandle fileHandleForUpdatingAtPath:logFilePath];
if (file)
{
unsigned long long maxFileOffset = [file seekToEndOfFile];
if (maxFileOffset > kDebugFileMaxSize)
{
// read file, trunicate, save, reopen
[file seekToFileOffset:maxFileOffset - kDebugFileMaxSize];
NSMutableData *data = [NSMutableData dataWithData:[file readDataToEndOfFile]];
[file seekToFileOffset:0];
[file writeData:data];
[file truncateFileAtOffset:kDebugFileMaxSize];
[file seekToEndOfFile];
}
}
else
{
[[NSFileManager defaultManager] createFileAtPath:logFilePath contents:nil attributes:nil];
file = [NSFileHandle fileHandleForUpdatingAtPath:logFilePath];
}
Anyone ever got something like that ?
I eventually just got rid of GTMLogger and moved to some other logger, thanks for anyone who might've read this :)

Resources