What is the simplest way to parse binary files in Cocoa Touch? - cocoa-touch

Say I have a binary file (generated with Java) containing a 32 bit int value. I'm currently using the following Objective-C code for parsing it:
NSString *path = [[NSBundle mainBundle] pathForResource:#"foo" ofType:#"dat"];
NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:path];
unsigned long intValue;
memcpy(&intValue, [[file readDataOfLength:4] bytes], 4);
intValue = NSSwapBigLongToHost(intValue);
[file closeFile];
My first question is to ask if it's the common way of doing things on the iPhone because I didn't find anything close to Java's DataInputStream.
My second question is related to the line of code with a memcpy. I don't undestand why the following part (more elegant, less "low-level") is not working instead:
[[file readDataOfLength:4] getbytes:&intValue];
I'm getting a warning on build:
'NSData' may not respond to '-getbytes:'
On execution, I'm getting:
'NSInvalidArgumentException', reason: '*** -[NSConcreteData getbytes:]: unrecognized selector sent to instance

For the last question, use getBytes (uppercase B).

Related

+[NSData bookmarkDataWithContentsOfURL:]: unrecognized selector sent to class

I am trying to resolve an alias file's original path using Objective-C(or maybe C++; it's an .mm file). Not being very much familiar, I am somehow missing + and - methods' usage. I am aware of them being class and instance methods respectively, but in practice, the following the code, with the indicated lines give me following warning and error(at build):
Class method '+bookmarkDataWithContentsOfURL:' not found (return type defaults to 'id')
-
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NSData bookmarkDataWithContentsOfURL:]: unrecognized selector sent to class 0x7fff88942cb8'
with 0x7fff88942cb8 being the NSData address as per lldb.
Which files should I make the changes in, to get bookmarkDataWithContentsOfURL:error: and URLByResolvingBookmarkData to work?
void *pathclass::resolveAliasFromURL(const char *filepath) const
{
NSError *error = nil;
NSString *filepathh = [[NSString alloc] initWithUTF8String:filepath];
NSData *bookmarkk = [NSData bookmarkDataWithContentsOfURL:filepathh]; /*problematic line*/
BOOL isstale = NO;
NSURL *actual = [NSURL URLByResolvingBookmarkData:bookmarkk bookmarkDataIsStale:isstale error:error];/*another problematic line, but build fails already*/
NSString *urlString = [actual absoluteString];
NSLog(#"%#",urlString);
}
If there are any other faults, please point out.
Your call to bookmarkDataWithContentsOfURL: is wrong in a few ways:
The signature looks like this:
+ (NSData *)bookmarkDataWithContentsOfURL:(NSURL *)bookmarkFileURL error:(NSError * _Nullable *)error;
First, the first parameter is of type NSURL*, not NSString*. Next, you miss off the error parameter completely (despite defining a variable for it). Lastly, the method is a class method on NSURL not NSData (NSData* is the return type).
So, first, make your file path into an NSURL*:
NSURL* bookmarkUrl = [NSURL URLWithString:filepathh];
Then, call the function using the proper arguments:
NSData *bookmarkk = [NSURL bookmarkDataWithContentsOfURL:bookmarkUrl error:&error];
You should check the returned value against nil - if it's nil, then an error occurred, and the error information will be contained inside error.
The documentation is quite helpful.
Your call to URLByResolvingBookmarkData:options:relativeToURL:bookmarkDataIsStale:error: has similar problems: you are missing several parameters, the first parameter should be NSURL, etc. Again, the documentation should help.

How do I get string input without using gets() or scanf/sscanf?

How can I get input from the console in Objective C? My concern is to avoid the possibility of buffer overflows, which is why I want to steer clear of gets() and scanf()/sscanf(). From searching StackOverflow, that's all I've been seeing. Is scanf() more secure in Objective C to the extent that I wouldn't have to worry?
Just recently, I found this example: I like it; but is there already a class for such?
printf("What is your name?");
NSFileHandle *input = [NSFileHandle fileHandleWithStandardInput];
NSData *inputData = [NSData dataWithData:[input availableData]];
NSString *inputString = [[NSString alloc] initWithData:inputData
encoding:NSUTF8StringEncoding];
inputString = [inputString stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
printf("Hello %s.n",[inputString UTF8String]);
The standard C I/O functions can be used, as you indicate. They are not more secure just because you are using Objective-C. To avoid buffer overflow issues use fgets rather and gets. Note you will be reading a C-style string and will need to convert it if you need an NSString.
At the Cocoa level you can use NSFileHandle as you show. Note that availableData returns all the available data not the next line, but as the console device is usually set to line mode what will be returned is the next line provided the user has not typed ahead.
At a lower level you can look at read.
HTH

Why my program can run in Xcode, but cannot running as a separate app?

My program loads some data from a file and then draws them.
The file-reading part is like this:
- (void)load_file
{
NSFileHandle *inFile = [NSFileHandle fileHandleForReadingAtPath:#"map_data"];
NSData *myData=[inFile readDataToEndOfFile];
NSString *myText=[[NSString alloc]initWithData:myData encoding:NSASCIIStringEncoding];
NSArray *values = [myText componentsSeparatedByString:#"\n"];
for (NSString *string in values) {
NSArray *lines=[string componentsSeparatedByString:#" "];
if ([lines count] != 2) break;
NSPoint point= NSMakePoint([lines[0] floatValue], [lines[1] floatValue]);
[points addObject:[NSValue valueWithPoint:point]];
}
[self setNeedsDisplay:YES];
}
When debugging, I put the data file in the directory of [NSBundle mainBundle], and the program works fine.
However, when I use achieve to take the app out, it never runs. I put the data file in the same path with the app, but it seems fail to load it.
Update
I tried to use c++, but still fails.
- (void)load_file
{
ifstream inf("map_data");
double x, y;
while (inf >> x >> y) [points addObject:[NSValue valueWithPoint:NSMakePoint(x, y)]];
inf.close();
}
I tried to change the build scheme to release and run, which is fine. But whenever I go directly into the finder of app and double click it, it does not work and seems nothing is loaded.
add the file to the project as a Resource (this will cause it to be copied into the app wrapper in the right spot)
use `[[NSBundle mainBundle] pathForResource:#"map_data" ofType:nil];
That should give you the path to the file. The file should not be manually copied, it should not be next to the app wrapper, nor should you [conjecture] ever try changing or replacing the file once it is in your app wrapper.
The reason why it seems to work sometimes is mere coincidence. You are passing a partial path to NSFileHandle and it happens that the current working directory of your app sometimes points to the right spot such that the data file is available.
I'm not sure how relative paths are handled by NSFileHandle, but usually you set up paths using the NSBundle class.
NSString *path = [[NSBundle mainBundle] pathForResource:#"myfile" ofType:#"ext"];
You can also simply initialize an NSString from the contents of a file, you don't need to first read it into an NSData using NSFileHandle.
NSString *text = [[NSString alloc] initWithContentsOfFile:path
encoding:NSASCIIStringEncoding error:nil];
(Use the error parameter, if you want proper error handling)

Limitations of NSMutableData for NSKeyedUnarchiver

I have read on another post (Archiving / Unarchiving results in initForReadingWithData incomprehensible archive) that you can't store more than 250kBytes on a NSMutableArray. Unfortunately, in order to recover such data with NSKeyedUnarchiver, you must use a NSMutableArray. I am trying to get back an image with a size around 500kB.
MTMessage *message = [NSKeyedUnarchiver unarchiveObjectWithData:data];
The error I get is :
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSKeyedUnarchiver initForReadingWithData:]: incomprehensible archive (0x0, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x0, 0x1)'
Apparently it's a pretty common situation (even-though i have not found a solution yet). Would you have any idea of how to bypass the use of NSMutableData.
Thank you.
EDIT : Actually it says that data has a size of 524 288 bytes, which is correct, so the problem might come from the unarchiver.
NSKeyedArchiver does not depend on an NSArray (immutable or not).
I'm also not aware of a bug correlated with NSKeydArchiver and depending on archive size.
The following code runs fine on Lion:
NSMutableData *data = [NSMutableData data];
for (uint32_t i = 0; i < 1024 * 1024; ++i)
[data appendBytes:&i length:sizeof(uint32_t)];
NSData *archive = [NSKeyedArchiver archivedDataWithRootObject:[NSMutableArray arrayWithObject:data]];
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:archive];
assert([data isEqual:[array lastObject]]);
Please provide more code for more insight in your actual problem. Are you maybe trying to unarchive an encoded image instead of an archive?

Converting File Path From NSString To NSURL

I'm working through Cocoa smoothly, but this problem seems so basic it cancels out all the cool stuff I learned. :/
I have a generated file path, and it needs to be in NSURL format. From research, this is the code I wrote:
NSLog(#"Old path = %#", pathToFile);
NSURL *xmlURL = [[[NSURL alloc] init] fileURLWithPath:pathToFile];
NSLog(#"New path = %#", [xmlURL absoluteString]);
And the output:
2010-01-27 15:39:22.105 MusicLibraryStats[28574:a0f] Old path = file://localhost/Users/[username]/Music/iTunes/iTunes%20Music%20Library.xml
2010-01-27 15:39:22.105 MusicLibraryStats[28574:a0f] New path = (null)
First off, the alloc-init shouldn't even be necessary; other people seem to get away with it. In this case, if I don't alloc-init, I get an 'unrecognized selector' error on that line. Of course, now I'm just getting plain old (null).
Where did I goof?
Thanks!
The [[NSURL alloc] init] is not just unnecessary, it's invalid. fileURLWithPath: is a class method, which means you can only call it on the class object (that is, NSURL itself). It does not produce a compile error because -(NSURL *)init returns an object of type id, and does not result in a runtime error because -(NSURL *)init actually returns nil, and messages sent to nil will just cascade another nil as their return value.
This code should work:
NSString* pathToFile = #"/this/is/a/path";
NSURL* url = [NSURL fileURLWithPath:pathToFile];
I found your problem.
-[NSOpenPanel URLs] returns an array of NSURL objects, which you treat as NSString objects. That's not right. You should use the following:
NSURL* url = [[oPanel URLs] objectAtIndex:0];
The debugger could've show you that if you looked at the pathToFile variable. Make sure to check it next time. :) Hovering a variable with your mouse should get you its type.
However, remember that there are situations where you will legitimately encounter another type than the one you expected. For instance, the private NSPathStore2 class is part of the NSString cluster, and you can do everything NSString supports on NSPathStore2 objects. (If this happens and you're not too sure, check the documentation to see if the type you expect is a cluster type. That's how they're called in the documentation.)