I want to get the first 8 bytes or so of a file without reading the whole file. I'm using NSData to operate on the data and such, but I don't want to slow down my application with excessive file reads and writes because in some cases I'm having to read a 200 kilobyte file just to extract the first 2 bytes of data from the file. Is there any way to only read or write a part of the file without reading or overwriting the whole thing in Xcode with the iOS SDK?
The file system that I'm using is just the default one that's accessible through the NSFileManager class (I don't know of any other iOS file system).
You may take advantage of the higher level NSFileHandle class. The NSFileHandle class is an object-oriented wrapper for a file descriptor. You use file handle objects to access data associated with files, sockets, pipes, and devices. For files, you can read, write, and seek within the file. For sockets, pipes, and devices, you can use a file handle object to monitor the device and process data asynchronously.
- (NSData *)readDataOfLength:(NSUInteger)length
You can get more info in official documentation NSFileHandle Class Reference
Use the standard C file API (either FILE* or int file descriptors). The caveat is that you have to properly convert the string path to a correct char* file path. Also, don't forget to close the file when done. Consider a category on NSData, something kinda like this...
+ (id)dataWithContentsOfFile:(NSString *)filePath numBytes:(NSUInteger)numBytes
{
void *bytes = malloc(numBytes);
NSData *result = [NSData dataWithBytesNoCopy:bytes length:numBytes];
char const *path = [[NSFileManager defaultManager] fileSystemRepresentationWithPath:filePath];
int fd;
if ((fd = open(path, O_RDONLY)) < 0 || read(fd, bytes, numBytes) != numBytes) {
result = nil;
}
close(fd);
return result;
}
Related
I'm uploading files to Dropbox, and I'm wondering if I can mark anything through NSFileManager to test to see whether a file has already been uploaded. I've been combing through the documentation and haven't found anything yet that could help.
So for instance, if I've uploaded a file called song.m4a, and the user changes the name of that file in the app, how would I be able to find out whether that file has been uploaded with the new name so that the file doesn't get uploaded again?
Are there any properties or attributes I could set to see if the file has been uploaded?
Thanks.
You can use hashing, e.g. you can calculate the MD5 hash of the file and store it in a local file on the phone, when the user tries to upload a file, you don't check its name, you simply recalculate the MD5 hash and check if it exists in your local file, if it does, then it was uploaded once before.
Edit:
You can convert anything to NSData and then calculate the hash of that NSData, e.g. in your case you can load the file like this
NSData* data = [NSData dataWithContentsOfFile:yourFilePath];
then you can hash it like this
- (NSString*)MD5:(NSData*)input
{
// Create byte array of unsigned chars
unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH];
// Create 16 byte MD5 hash value, store in buffer
CC_MD5(input.bytes, input.length, md5Buffer);
// Convert unsigned char buffer to NSString of hex values
NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++)
[output appendFormat:#"%02x",md5Buffer[i]];
return output;
}
and don't forget to import
#import <CommonCrypto/CommonDigest.h>
I need to download large files from the Internet , and save it to local disk.
At first, i save the data like this:
- (void)saveToLocalFile:(NSData *)data withOffset:(unsigned long long)offset{
NSString* localFile = [self tempLocalFile];
dispatch_async(mFileOperationQueue_, ^{
NSFileHandle* fileHandle = [NSFileHandle fileHandleForWritingAtPath:localFile];
if (fileHandle == nil) {
[data writeToFile:localFile atomically:YES];
return;
}
else {
[fileHandle seekToFileOffset:offset];
[fileHandle writeData:data];
[fileHandle closeFile];
}
});
}
As AFNetworking use NSOutputstream to save data to local like this:
NSUInteger length = [data length];
while (YES) {
NSInteger totalNumberOfBytesWritten = 0;
if ([self.outputStream hasSpaceAvailable]) {
const uint8_t *dataBuffer = (uint8_t *)[data bytes];
NSInteger numberOfBytesWritten = 0;
while (totalNumberOfBytesWritten < (NSInteger)length) {
numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)];
if (numberOfBytesWritten == -1) {
break;
}
totalNumberOfBytesWritten += numberOfBytesWritten;
}
break;
}
if (self.outputStream.streamError) {
[self.connection cancel];
[self performSelector:#selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError];
return;
}
}
What are the advantages to use NSOutputstream than NSFileHandle when writing a file ?
What are the advantages in terms of performance ?
There are several different technologies for reading and writing the contents of files, nearly all of which are supported by both iOS and OS X. All of them do essentially the same thing but in slightly different ways. Some technologies require you to read and write file data sequentially, while others may allow you to jump around and operate on only part of a file. Some technologies provide automatic support for reading and writing asynchronously, while others execute synchronously so that you have more control over their execution.
Choosing from the available technologies is a matter of deciding how much control you want over the reading and writing process and how much effort you want to spend writing your file management code. Higher-level technologies like Cocoa streams limit your flexibility but provide an easy-to-use interface. Lower-level technologies like POSIX and Grand Central Dispatch (GCD) give you maximum flexibility and power but require you to write a little more code.
Reading and Writing Files Asynchronously
Because file operations involve accessing a disk (possibly one on a network server), performing those operations asynchronously is almost always preferred. Technologies such as Cocoa streams and Grand Central Dispatch (GCD) are designed to execute asynchronously at all times, which allows you to focus on reading and writing file data rather than worrying about where your code executes.
Processing an Entire File Linearly Using Streams
If you always read or write a file’s contents from start to finish, streams provide a simple interface for doing so asynchronously. Streams are typically used for managing sockets and other types of sources where data may become available over time. However, you can also use streams to read or write an entire file in one or more bursts. There are two types of streams available:
Use an NSOutputStream to write data sequentially to disk.
Use an
NSInputStream object to read data sequentially from disk.
Please go through the Apple Documentaion for code explanation.
I want to read a list (in plain text) from a remote file line by line.
I have found some answers but they're not the ones I'm looking for.
p.s. I've been programing in objective-c and developing in iOS for about 2 months, I'm a rookie i might not understand or recognize some terms. Please answer like you are talking to a beginner.
If i am not wrong you just want to read a text from remote file, so here it is.
NSString * result = NULL;
NSError *err = nil;
NSURL * urlToRequest = [NSURL URLWithString:#"YOUR_REMOTE_FILE_URL"];//like "http://www.example.org/abc.txt"
if(urlToRequest)
{
result = [NSString stringWithContentsOfURL: urlToRequest
encoding:NSUTF8StringEncoding error:&err];
}
if(!err){
NSLog(#"Result::%#",result);
}
To load the remote txt file, you should take a look at NSURLConnection or AFNetworking (there are other possibilities, these two are probably the most common).
You will then get the content of the file. Depending on what you intend to do with it, you may have to parse it, either with something as simple as -[NSString componentsSeparatedByString:] or with something a bit more powerful like NSScanner.
There are three steps involved in loading a file
create the object that specifies the location of the file
call the appropriate NSString class method to load the file into a
string
handle the error if the file is not found
In step 1, you need to either create an NSString with the full path to the file in the file system, or you need to create an NSURL with the network location of the file. In the example below, the code creates an NSURL since your file is on the network.
In step 2, use the stringWithContentsOfFile method to load a file from the file system, or the stringWithContentsOfURL method to load a file from the network. In either case, you can specify the file encoding, or ask iOS to auto-detect the file encoding. The code below auto detects while loading from the network.
In step 3, the code below dumps the file to the debug console if successful or dumps the error object to the console on failure.
Missing from this code is multithreading. The code will block until the file is loaded. Running the code on a background thread, and properly notifying the main thread when the download is complete, is left as an exercise for the reader.
NSURL *url = [NSURL URLWithString:#"www.example.com/somefile.txt"];
NSStringEncoding encoding;
NSError *error;
NSString *str = [NSString stringWithContentsOfURL:url usedEncoding:&encoding error:&error];
if ( str )
NSLog( #"%#", str );
else
NSLog( #"%#", error );
My app is currently parsing CSV files from a web service by using a combination of componentsSeparatedByCharactersInSet and componentsSeparatedByString methods.
As the files are quite large (> 1 mb on average), parsing takes a couple of seconds on an iPad, which is too slow. The memory footprint of my solution is an issue too (I am holding the full text file in memory).
This is why I am looking for a faster and more memory-efficient solution. I came across CHCSVParser which can parse NSInputStreams directly, e.g.
NSInputStream *stream = [NSInputStream inputStreamWithFileAtPath:file];
CHCSVParser * p = [[CHCSVParser alloc] initWithInputStream:stream
usedEncoding:&encoding delimiter:';'];
(Source from the sample project on CHCSVParser)
My question:
How can I get an NSInputStream as the result of an NSURLRequest? (Currently I am getting the whole CSV file as a NSData object and converting it to NSString in order to parse it).
Could I use the NSInputStream from an NSURLRequest directly with CHSVParser?
Would you generally recommend using CHCSVParsers initWithInputStream method with a NSURLRequest or rather download the document to memory and parse if after the full download?
Download the file to disk using NSURLConnectionDelegate and NSOutputStream (that way you use as little memory as possible while downloading) and then open an NSInputStream to the same file and pass it into CHCSVParser.
This one is going to kill me. I'm so close to getting this done except for this one stupid problem. And I am not sure I will be able to adequately describe the problem, but I'll try.
My enterprise app uses the iPhone camera to take pictures of receipts of purchases made by our field personnel. I used a real cool API for turning the jpeg data to base 64 (https://github.com/nicklockwood/Base64) to send via TCP connection to the VB 2010 server, which reads it as a text string and converts it back to a binary.
When the base64 file is created, it is first saved to disk on the phone, because there may be more images. Then when ready to send, the process will read each base64 file and send it one at a time.
The text string created by the base64 function is quite large, and at first it was only sending about 131,000 bytes, which would convert back to binary easily enough but would render about 1/4 to 1/3 of the image. I just figured that the data was being truncated because the app was trying to get ahead of itself.
So then I found a nice snippet that showed me how to use the NSStreamEventHasSpaceAvailable event to split the base64 string into several chunks and send them sequentially. (http://www.ios-developer.net/iphone-ipad-programmer/development/tcpip/tcp-client) That works great insofar as it sends the full file -- that is, the resulting file received by the server is the correct size, the same as the base64 file before it's sent.
The problem here is that at some point the file received by the server is corrupted because it seems to start all over at the beginning of the file... in other words, the data starts to repeat itself.
The odd part is that the repeating part starts at exactly the same spot in the received file every time: at position 131016. It doesn't start the repetition at the end of the file, it just interrupts the file at that point and jumps back to the beginning. And it happens that that was the size of the file that was sent before I started using the HasSpaceAvailable event. I can't figure out what the significance of the value 131,016 is. Maximum buffer size somewhere?
Using all kinds of NSLogs and breakpoints, I have pretty much determined that the data is leaving the phone that way, and not being scrambled by the server. I also wrote in an NSMailComposeViewer method that would email me the base64 file as an attachment, and it comes through perfectly.
Here is the code for when the file is read from disk and sent to the server:
int i;
for (i = 0; i < [imageList count];i++){
NSArray *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory= [documentsPath objectAtIndex:0]; //Get the docs directory
NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:imageFileName];
imageFileName = [imageList objectAtIndex:i] ;
NSLog(#"Image index: %d - image file name: %#",i,imageFileName);
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:imagePath];
if(fileExists == YES){
NSString *imageReceipt = [NSString stringWithContentsOfFile:imagePath encoding:NSASCIIStringEncoding error:nil];
int32_t imageStringLen = [imageReceipt length];
NSString *imageSize = [NSString stringWithFormat: #"%d",imageStringLen];
NSString *currentImage = [NSString stringWithFormat:#"image,%#,%#,%#",imageFileName,imageSize,imageReceipt]; //creates a CSV string with a header string with the filename and file size, and then appends the image data as the final comma-separated string.
data = [[NSMutableData alloc] initWithData:[currentImage dataUsingEncoding:NSASCIIStringEncoding]];
[outputStream write:[data bytes] maxLength:[data length]];
And then here is the code that uses the HasSpaceAvailable event:
case NSStreamEventHasSpaceAvailable:
if (data != nil)
{
//Send rest of the packet
int ActualOutputBytes = [outputStream write:[data bytes] maxLength:[data length]];
int totalLength = [data length];
if (ActualOutputBytes >= totalLength)
{
//It was all sent
data = nil;
}
else
{
//Only partially sent
[data replaceBytesInRange:NSMakeRange(0, ActualOutputBytes) withBytes:NULL length:0]; //Remove sent bytes from the start
}
}
break;
(I especially like this code because it would allow placing a ProgressView control on the screen.)
The network stream event handler code is in the root view controller, but the image data in base64 is being sent from another view controller. My instinct tells me that this is not a problem because it has worked fine until now, but with much shorter strings.
Now, there's one other issue that may be related -- and probably is. I can't seem to complete the transfer of the data unless I close the app. The server doesn't see it until the connection is closed, I guess. I have tried placing [outputStream close] in various places in the code to no avail. I've also tried terminating the base64 string with a linefeed or carriage return or both.
The server is programmed to save the file when it has seen the correct number of bytes, but that never happens until the app is closed. I know by using WireShark on the server that some of the data is being received, but the remaining data, as I have said, doesn't arrive until the app is closed.
I suspect that this last part (completing the transfer) is the problem, but for the life of me, I can't find anything online that addresses this, unless I am just too ignorant to know what search terms to use.... which is highly likely.
I hope I have given enough information. Can anyone help me?
EDIT
The solution to the problem appeared to be different than what was suspected in the original answer. Through the discussion in the comments the QP has been led to a solution. Here is a summary:
The transmission of data through a raw TCP socket requires thorough handling of the enqueing logic, which was not properly taken into account by the QP. I recommended to use the socket library CocoaAsyncSocket which handles this part of the task and which in turn led the QP to a working solution.
Original Answer:
I'd guess NSString is not up to the task. It's made to hold, well, strings.
Try to read the file into an NSData directly instead and sending it as binary. (Its ascii in the end, isn't it?) Besides, this will be much more resource friendly than your current code.