Limitations of NSMutableData for NSKeyedUnarchiver - objective-c

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?

Related

Save NSData when memory mapped

What I am hoping to do may or may not be possible but I'll give it a shot. I am attempting to load huge multiple gigabyte text files. I am currently using an memory mapped NSData and only loading portions at a time and just infinite scrolling through it and load the currently visible chunk and it all is happy and it barely breaks a sweat with memory (<30mb).
Next I wanted to add editing of the data. I casually assumed I could just edit that memory mapped data and have it reflect on disk. This does not seem to be the case. Is there a way I can say hey the data changed! update the file. I am a little vague on how the file and NSData are linked.
Here is how it is currently working.
// create the memory mapped data:
self.fileData = [NSMutableData dataWithContentsOfURL:url options:NSDataReadingMappedAlways error:outError];
//load a chunk
self.currentRange = NSMakeRange(0, 4096);
void* data = malloc(4096);
[self.fileData getBytes:data range:self.currentRange];
NSString* currentView = [[NSString alloc] initWithBytes:data length:4096 encoding:NSUTF8StringEncoding];
//replace a chunk:
NSData* currentData = [[self.textArea string] dataUsingEncoding:NSUTF8StringEncoding];
[self.fileData replaceBytesInRange:self.currentRange withBytes:[currentData bytes] length:[currentData length]];
//If I close and re-open it doesn't actually work
Is there a way of doing this or would it be possible to create an NSOutputStream to the URL and somehow stream portions of it back to disk? Is there another way to write/save memory mapped data? If I try to do a writeToURL: or comparable function it will load it all to actual memory which is really no good.

NSString and crashes

I have this code:
-(void)getData:(NSString *)data: (id) tv: (id) soc
{
NSLog(#"\nin get data with data\n");
NSLog(data);
After a few hours the app crashes and it reaches in get data with data and doesn't print the data so it's crashing on the print of the data. The debug references the failure in something like a string length function. In XCode, data has a warning that it is not a string literal and potentially insecure. Now my experience tells me the most likely culprit is data is somehow null. But it also printed in the log something that looks like it received a typical message. it said it has 131 bytes from the socket. When i tested and it crashed last time it has 189 bytes. but it never prints it.
The data is sent in from the socket liket this in receive data:
UInt8 buffer[len];
NSLog(#"Received %d bytes from socket %d\n",
len, CFSocketGetNative(s));
CFDataGetBytes(df, range, buffer);
NSString *oldtext = [mTextViewAlias text];
char buffer2[len];
for(int a=0; a<len; a++)
buffer2[a]=buffer[a];
NSMutableData *buffer3 = [[NSMutableData alloc] init];
[buffer3 appendBytes:buffer2 length:len];
NSString *newdata = [[NSString alloc] initWithData: buffer3 encoding:NSASCIIStringEncoding];
and the call to the class method that is crashing when it prints the data is just:
[mytelnet getData:newdata:mTextViewAlias:(__bridge id)(s)];
Could i have a memory leak or something and after a few hours i'm out of memory causing the assignment of new data to be null even if i start out with some 100 bytes? Would it crash only when i tried to print data in the nslog and not earlier if it failed to allocate memory?
Mike
Instead of
NSLog(data);
use
NSLog(#"%#",data);
That may not solve your root cause but you get rid of the warning and may get some more reasonalbe and helpful debug output.
You might get rid of that crash too. Hovever this is not a promise.

Archiving / Unarchiving results in initForReadingWithData incomprehensible archive

I've implemented an save on applicationWillTerminate and load on applicationWillFinishLoading.
There is a complete object tree, all implement the NSCoding protocol and I've check the types I enter.
One of the classes also stores an NSMutableData to the NSKeyedArchive, which I suspect might break unarchiving occasionally. Weirdly enough, sometimes it works and sometimes it doesn't. I suspect some content in the NSMutableData will break the archiving.
I use encodeObject on all objects, except for the bools and int where I use the correct corresponding method (encodeBool:forKey: and encodeInt:forKey:)
To be more clear: the code really does work, sometimes is it able to rebuild a rather complete object graph, just not all the time.
The error message I get is:
initForReadingWithData incomprehensible archive 0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30
Added: code which is failing, it an NSMutableData of 10+ MB
- (void)encodeWithCoder:(NSCoder*)encoder {
[encoder encodeObject:self.encodedMessage forKey:#"EncodedMessage"]; //NSData
[encoder encodeObject:self.data forKey:#"Data"]; //NSMutableData (10+MB)
[encoder encodeObject:self.header forKey:#"Header"]; //NSString
[encoder encodeObject:self.fileName forKey:#"FileName"]; //NSString
[encoder encodeInt:self.dataStartIndex forKey:#"DataStartIndex"]; //int
[encoder encodeInt:self.dataEndIndex forKey:#"DataEndIndex"]; //int
}
- (id)initWithCoder:(NSCoder*)decoder {
if (self = [super init]) {
self.encodedMessage = [decoder decodeObjectForKey:#"EncodedMessage"]; //NSData
self.data = [decoder decodeObjectForKey:#"Data"]; //NSMutableData
self.header = [decoder decodeObjectForKey:#"Header"]; //NSString
self.fileName = [decoder decodeObjectForKey:#"FileName"]; //NSString
self.dataStartIndex = [decoder decodeIntForKey:#"DataStartIndex"]; //int
self.dataEndIndex = [decoder decodeIntForKey:#"DataEndIndex"]; //int
}
return self;
}
When I remove the self.data encoding and decoding it always seem to work.
It also fails with smaller sized self.data. Doesn't seem size but content issue?
Tried to open the file when I did write the nsmutabledata to it, the propertly list editor displays the error:
"Conversion of string failed. The string is empty."
plutil also gives this error:
"$ plutil -lint nzbvortex.state nzbvortex.state: Conversion of string failed. The string is empty."
FWIW I have also come across this problem and here is what I've found.
The bytes reported 0x62, 0x70, 0x6c, etc., are part of the magic string "bplist" at the start of a binary property list, which NSKeyedArchiver uses by default.
A binary property list stores metadata in a trailer (i.e. at the end of the data). So if it gets truncated, the entire plist becomes unreadable.
If you want to check whether that's what has happened to you, you can use NSPropertyListReader_binary1 from Cocotron (http://code.google.com/p/cocotron/source/browse/Foundation/NSPropertyList/) to see how the file format works.
Hope this helps someone!
It seems that store more than around 230000 bytes via an NSMutableArray will cause the NSKeyedArchiver to create a broken plist file.
220000 works, 250000 didn't. Did search for the exact amount that is allowed.
For the bool and int, there are two methods: encodeBool:forKey: and encodeInt:forKey: (taken from the NSKeyedArchiver reference).
For the NSMutableData, you should archive them with encodeObjectForKey: and unarchive them with decodeObjectForKey:.
You can refer to this useful guide for more cases.

Sqlite - "incomprehensible archive" problem

I am working on a iphone/Ipod touch project involving saving and fetching data from an sqlite database. It is working fine, but at some points in development i get this error (when the app starts):
erminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSKeyedUnarchiver initForReadingWithData:]: incomprehensible archive (0x4e, 0x49, 0x42, 0x41, 0x72, 0x63, 0x68, 0x69)'
2009-11-09 10:08:28.810 FagNavigasjon[1627:20b]
Previously i have only been able to solve this problem by reverting to earlier point in develepment and continuing. This time i have found out that if i comment out a certain part of the code, the app doesnt crash.
while(sqlite3_step(selectstmt3)==SQLITE_ROW){
/*
NSString *navn=[[NSString alloc] initWithUTF8String: (char *)sqlite3_column_text(selectstmt3, 0)];
NSString *telefon=[[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(selectstmt3, 1)];
NSString *fodselsar=[[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(selectstmt3, 2)];
NSString *ariframtiden=[[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(selectstmt3, 3)];
NSString *verneplikt=[[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(selectstmt3, 4)];
NSString *utdannelse=[[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(selectstmt3, 5)];
NSString *epost=[[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(selectstmt3, 6)];
appDelegate.navn=navn;
appDelegate.epost=epost;
appDelegate.telefon=telefon;
appDelegate.fodselsar=fodselsar;
appDelegate.studiear=ariframtiden;
appDelegate.utdannelse=utdannelse;
appDelegate.verneplikt=verneplikt;
*/
}
But of course the app doesnt load the data anymore. This code previously worked, and i have not changed it prior to this error. I have, however, added new records to the database, but these seem okay when i review them in SQLite manager. Can anyone tell me what can cause this problem? I am thankful for any responses!
Have you tried uncommenting every line one by one to see which line is causing the problem? If you know which line is causing it try to see if the data in the database is correct, also if you are using version control try reverting your database back to before you added the new records.
p.s.
Have you looked at Core Data? It could make your life a lot easier.
Just for the records: i have resolved this issue, though i still dont really understand how. As I knew which table caused the problem. I went in there using SQLlite manager and changed the values om some varchar fields, and the problem went away... The old values looked ok, though. Wish i could explain why this solved it...

What is the simplest way to parse binary files in 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).