Archiving / Unarchiving results in initForReadingWithData incomprehensible archive - objective-c

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.

Related

How to append NSMutable array to existing file

I am newbie in coding and trying to learn objective c, I tried searching my question on internet and stack overflow but not getting proper solution.
I am trying to save some data into array and after that I want to save that subarray into main array and after that main array into file.
below code is working properly for me but the data is not appending in file .
self.details = [[NSMutableArray alloc]init];
[self.details insertObject:self.firstName atIndex:0];
[self.details insertObject:self.lastName atIndex:1];
[self.details insertObject:[NSNumber numberWithLong:self.depositAmount] atIndex:2];
[self.details insertObject:[NSNumber numberWithInt:self.accountNumber] atIndex:3];
for (int i=0; i<self.details.count;i++) {
NSLog(#"%#",self.details[i]);
}
self.mainarray = [[NSMutableArray alloc]init];
[self.mainarray addObjectsFromArray:self.details];
NSString *path = #"/Users/testapp/data";
[self.mainarray writeToFile:path atomically:YES];
enter code here
for (int i=0; i<self.mainarray.count;i++) {
NSLog(#"%#",self.mainarray[i]);
}
writeToFile:path is actually deprecated (as you can see here https://developer.apple.com/documentation/foundation/nsdata/1408033-writetofile?language=objc)
In general to do this it depends on what you mean by append? If its literally just dump the data to end ( not necessarily making the file a legit array) then you can open a file in append mode, which you could use an output stream for (https://developer.apple.com/documentation/foundation/nsoutputstream/1564841-outputstreamtofileatpath?language=objc)
However, I am figuring that you want to append the new array objects to the file such that the file is structred. To do that you would need to read in the file and somehow decode that into an array. You would need a way to represent your data such as with JSON (https://developer.apple.com/documentation/foundation/nsjsonserialization) so that you can save the array into the file and vice versa.
But then you would need to read the file, and then add your new structred data so the file would still make sense. There are lots of ways to handle files (NSCoding, C files, etc..) but a simple way would to use NSOutputStream(https://developer.apple.com/documentation/foundation/nsoutputstream) and NSInputStream (https://developer.apple.com/documentation/foundation/nsinputstream?language=objc)

How to reliably retrieve NSData objects from NSInputStream in XCode

So my application works along these lines:
An iPod continuously sends NSDictionaries that contain: an image encoded in JPEG and some image properties as NSStrings.
The NSDictionary is encoded using NSPropertyListSerialization with the format BinaryFormat_v1_0 and sent in packets of 1024 bytes via NSStream to the central computer running an app on OSX.
The OSX app receives the data packets, continuously appending to a single NSMutableData object, until it sees the first packet of the next NSData object (which in binary format I've found starts as 'bplist').
The NSData is converted back to an NSDictionary to be used by the OSX app, by calling NSPropertyListSerialization.
Once the NSData was successfully converted (or not),the NSData object is set back to zero to start reading the next round of packets.
A few more notes: both the NSInputStream and NSOutput streams are running on their respective device's currentRunLoop in NSDefaultRunLoopMode.
When running this process, sometimes the conversion back to NSDictionary works fine with no errors (about 1/3 of the attempts), but the other times the conversion returns this error:
Error: Failed to convert NSData to NSDict : Error Domain=NSCocoaErrorDomain Code=3840 "Unexpected character b at line 1" UserInfo={NSDebugDescription=Unexpected character b at line 1, kCFPropertyListOldStyleParsingError=Error Domain=NSCocoaErrorDomain Code=3840 "Conversion of string failed." UserInfo={NSDebugDescription=Conversion of string failed.}}
Following are the parts of the program that parse the data from the stream:
... method to handle stream events:
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
switch(eventCode) {
case NSStreamEventHasBytesAvailable: {
uint8_t buf[1024];
unsigned int len = (unsigned)[(NSInputStream *)aStream read:buf maxLength:1024];
if(len) {
[self handleEventBuffer:buf WithLength:len];
}
...
... and the method that takes care of the data:
-(void)handleEventBuffer:(uint8_t*)buf WithLength:(unsigned int)len {
...
NSString *bufStr = [NSString stringWithFormat:#"%s",(const char*)buf];
if ([bufStr containsString:#"bplist00"] && [self.cameraData length] > 0) {
// Detected new file, enter in all the old data and reset for new data
NSError *error;
NSDictionary *tempDict = [[NSDictionary alloc] init];
tempDict = [NSPropertyListSerialization propertyListWithData:self.cameraData
options:0
format:NULL
error:&error];
if (error != nil) {
// Expected good file but no good file, erase and restart
NSLog(#"Error: Failed to convert NSData to NSDict : %#", [error description]);
[self.cameraData setLength:0];
}
...
[self.cameraData setLength:0];
[self.cameraData appendBytes:buf length:len];
} else {
// Still recieving data
[self.cameraData appendBytes:buf length:len];
}
So, the question that I'm getting at is:
How can I fix my parsing method to give me reliable results that don't randomly fail to convert?
OR is there a better way than this to parse buffer streams for this purpose?
OR am I just doing something stupid or missing something obvious?
You appear to be relying on each write to the stream resulting in a matching read of the same size, do you know this is guaranteed by NSStream? If not then any read could contain parts of two (or more) of your encoded dictionaries, and you would get the parsing errors you see.
Alternative approach:
For each encoded dictionary to send:
Write end:
Send a message containing the size in bytes of the encoded dictionary that will follow.
Write the encoded dictionary in chunks, the last chunk may be short
Repeat
Read end:
Read the size message specifying its exact length in bytes.
Read the encoded dictionary in chunks, making sure you read only the number of bytes reported by (1).
Repeat.
Provided you are using a reliable communication stream this should enable you to read each encoded dictionary reliably. It avoids you trying to figure out where the boundary between each encoded dictionary is, as that information is part of your protocol.
HTH

incompatible pointer to integer conversion sending viewcontroller * to parameter of type BOOL

I was trying to work out how to read a text file stored on a web server and display the contents in a text view. I have followed the documentation from Apple website NSURLConnection of how to establish a NSURLConnection and receiveData and display the received data.
I have created a button where I want to load the text view on button click. For that I wrote this method
- (void)loadWeb:(BOOL)animated
{
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://nowayweb.com/mytext.txt"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0];
NSURLConnection *myConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (myConnection) {
downloadedData = [[NSMutableData data] retain];
}
else{
NSLog(#"Error");
}
}
But I am getting a warning in my .m file which can be seen in the screenshot as shown here:
It works fine and I can view the text in the text view, but I am wondering where am I making the mistake. If somebody throws some light on this, would be helpful.
Or is there a better way to load the contents from web by button click. Any help is appreciated.
Thanks
self is a pointer to an object, not a BOOL value. You should do this:
[self loadWeb: YES];
Except your method does not seem to use the parameter anyway, so you might as well get rid of it.
[self loadWeb];
and
- (void)loadWeb
{
// all the stuff inside
}
The reason it worked for you is a) you weren't using the parameter, b) the compiler will automatically convert self into a BOOL by chopping off all but the least significant byte of the pointer. If you had been using the parameter, most of the time it would have been YES by chance and occasionally it would have been NO by chance.
ObjC's BOOL is not a real boolean type. it is a typedef for a signed char.
The method's signature is:
- (void)loadWeb:(BOOL)animated
the expression [self loadWeb:self]; doesn't make sense, and it's not a real bool conversion. the compiler warns you that you are converting a pointer to a signed char.
It should read either:
[self loadWeb:YES];
-or-
[self loadWeb:NO];
-or-
BOOL someBOOLVariableOrParameter = ...; // YES or NO
[self loadWeb:someBOOLVariableOrParameter];

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?

How do I serialize an object to file using NSKeyedArchiver in NSPropertyListXMLFormat_v1_0 format?

Hopefully that title is clear enough. Anyways, it seems:
[NSKeyedArchiver archiveRootObject:rootObject toFile:path];
is restricted to NSPropertyListBinaryFormat_v1_0.
I need the format to be human readable [so xml]. If you're interested, this is part of project I'll eventually put up on github with a blog post about "How to Unit Test GPS Applications"
:-)
You have to use NSPropertyListSerialization to convert it. Basically, assuming the object that you're serializing is called myObject and the ultimate file is stored in myPath, you would serialize it to XML using something like the following code fragment:
NSString *error = nil;
NSData *mySerializedObject = [NSKeyedArchiver archivedDataWithRootObject:myObject];
NSData *xmlData = [NSPropertyListSerialization dataFromPropertyList:mySerializedObject
format:NSPropertyListXMLFormat_v1_0
errorDescription:&error]:
if( xmlData ) {
[xmlData writeToFile:myPath atomically:YES];
} else {
NSLog(error);
[error release];
}
Instead of using the class method, try creating an instance of NSKeyedArchiver and using setOutputFormat:NSPropertyListXMLFormat_v1_0 to specify XML format. The only downside is it'll take a few extra lines of code since to actually handle the serialization, it won't just return an NSData object in one step.
You will have to encode the object class by using method
-(void) encodeWithCoder(NSCoder *)encoder {
[coder encodeObject:name forKey:#"name"];
[coder encodeObject:address forKey:#"address"];
[coder encodeObject:position forKey:#"position"];
}