I usually encode my data in a NSFileWrapper like this (I leave out the NSFileWrapper bit):
-(NSData*)encodeObject:(id<NSCoding>)o {
#autoreleasepool {
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:o forKey:#"data"];
[archiver finishEncoding];
return data;
}
}
And I usually get my data back when doing this:
- (id)decodeObjectFromWrapperWithPreferredFilename:(NSString *)p {
NSFileWrapper *wrapper = [self.fileWrapper.fileWrappers objectForKey:p];
if (!wrapper) {
NSLog(#"Unexpected error: Couldn't find %# in file wrapper!", p);
return nil;
}
NSData *data = [wrapper regularFileContents];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSLog(#"%#", [unarchiver decodeObjectForKey:#"data"]);
return [unarchiver decodeObjectForKey:#"data"];
}
Sometimes, I get NSData back (it is not nil), but [unarchiver decodeObjectForKey:#"data"] will return NIL. It appears as if there is no object for the key #"data" even though there should be. I guess something must have gone wrong when encoding, but I'm not sure how to trouble shoot this. Can I just take whatever is in data and encode it, not worrying if it has got the right key? I mean there should only ever be one key "data".
Why is your code so complicated :) The NSKeyedArchiver class has helper methods that will do what you want more simply :
// to turn an object into NSData
return [NSKeyedArchiver archivedDataWithRootObject:o];
// To turn NSData into your object again
return [NSKeyedUnarchiver unarchiveObjectWithData:data];
Related
I have this is on the top of my program:
#property (strong, nonatomic) NSMutableData *data;
I thought this would allow me to store the value from every time this method runs:
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests
{
for (CBATTRequest *request in requests) {
NSString *stringValue = [[NSString alloc] initWithData: [request value] encoding:NSUTF8StringEncoding];
// Have we got everything we need?
if ([stringValue isEqualToString:#"EOM"]) {
// We have, so show the data,
[self.textview setText:[[NSString alloc] initWithData:self.data encoding:NSUTF8StringEncoding]];
}
// Otherwise, just add the data on to what we already have
[self.data appendData:[request value]];
}
}
This method waits for a write request to be received and stores the value in a string. I have a core bluetooth central that is sending three blocks of data. This is to get around the data transfer size restriction within bluetooth LE. The problem is I can't get the three values stored. I am trying to not just store the last value but add the new value to the end of a nsstring or nssdata every time the method is called. Any help would be greatly appreciated. I thought the property at the top would allow me to do it but it either only stores the last value or nothing at all. I am not used to the ways of objective c yet. Thanks.
Even this doesn't write anything to self.data:
NSString * result = [[requests valueForKey:#"value"] componentsJoinedByString:#""];
NSData* data = [result dataUsingEncoding:NSUTF8StringEncoding];
[self.data appendData:data];
// Log it
NSLog(#"%#",self.data);
You should use NSMutableArray instead of NSString as a mutable string.
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests
{
NSMutableString *stringValue = [[NSMutableString alloc] init];
for (CBATTRequest *request in requests) {
[stringValue appendString:[[NSString alloc] initWithData:[request value] encoding:NSUTF8StringEncoding]];
// Have we got everything we need?
if ([stringValue isEqualToString:#"EOM"]) {
// We have, so show the data,
[self.textview setText:[[NSString alloc] initWithData:self.data encoding:NSUTF8StringEncoding]];
}
// Otherwise, just add the data on to what we already have
[self.data appendData:[request value]];
}
Remember kids when dealing with NSMutableData always initialize it!
_data = [[NSMutableData alloc] init];
This fixed the null problem for me.
I have a NSMutableArray and a NSString . These two are archived to NSData and add to a NSMutableData Object.
How can I access each data from NSMutableData Object.
NSData *dataArray= [NSKeyedArchiver archivedDataWithRootObject:mutableArray];
NSData *dataTouchedNumer=[NSKeyedArchiver archivedDataWithRootObject:stringValue];
NSMutableData *mutableData=[[NSMutableData alloc]init];
[mutableData appendData:dataArray];
[mutableData appendData:dataTouchedNumer];
You can't do this the way you are showing. If you append two NSData objects together into a single mutable data object, there is no way to separate them later. Try this instead:
To archive the two objects:
NSMutableArray *mutableArray = ... // your mutable array
NSString *stringValue = ... // your string
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:mutableArray forKey:#"array"];
[archiver encodeObject:stringValue forKey:#"string"];
At this point, data contains the two objects. Do what you need with the data (save it for example).
To get your objects back:
NSData *data = ... // the archived data
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSMutableArray *mutableArray = [unarchiver decodeObjectForKey:#"array"];
NSString *stringValue = [unarchiver decodeObjectForKey:#"string"];
According to the docs, "archivedDataWithRootObject: returns an NSData object containing the encoded form of the object graph whose root object is given." So your mutableData object contains 2 such encoded object graphs. The question is what kind of data you want to read out of mutableData. It probably does not make much sense, to read simply all bytes with [mutableData bytes], or part of it with getBytes:length: or getBytes:range:.
I have two NSData objects I want to store within a third NSData object. The idea is I want to make it easy when I later decode the larger object, to get at the two smaller objects independently of one another, without worrying about their relative sizes or datatypes.
It appears the best way to do this is to use NSKeyedArchiver to create a sort of root-level key-value structure within the larger NSData object, in which I can store the two smaller objects within separate keys. That's what I've attempted to do here:
NSData *data1Before = [#"abcdefgh" dataUsingEncoding:NSUTF8StringEncoding];
NSData *data2Before = [#"ijklmnop" dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData *allData = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:allData];
[archiver encodeObject:data1Before forKey:#"key1"];
[archiver encodeObject:data2Before forKey:#"key2"];
[archiver finishEncoding];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:allData];
NSData *data1After = [unarchiver decodeObjectForKey:#"key1"];
NSData *data2After = [unarchiver decodeObjectForKey:#"key2"];
[unarchiver finishDecoding];
NSString *string1After = [NSString stringWithUTF8String:[data1After bytes]];
NSString *string2After = [NSString stringWithUTF8String:[data2After bytes]];
NSLog(#"after1: %#",string1After);
NSLog(#"after2: %#",string2After);
The problem is, when you run this code over and over, you get all sorts of different results coming from the NSLog statements- someetimes special characters get appended to the end of the strings, sometimes they're just NULL.
It appears this corruption has something to do with this "double-encoding" process I'm using. When I modify the code so that the NSKeyedArchiver just calls encodeObject directly on NSStrings, rather than on NSData objects, I can later use decodeObjectForKey and get at those strings without any problems- no corruption at all.
Is there a better way of doing this than using NSKeyedArchiver? Or am I using it incorrectly?
Thanks to rmaddy for his answer above- I just needed to replace this:
[NSString stringWithUTF8String:[data1After bytes]];
with this:
[[NSString alloc] initWithData:data1After encoding: NSUTF8StringEncoding];
and that fixed it.
I have an NSDictionary containing some objects: an NSSet of UITouches, a UIEvent, and an NSString.
When I try to encode the dictionary to NSData, the string encodes properly. I had an error with the UITouches being encoded, but I found a way to extend the class with some code so that a UITouch can be encoded. However, I still can't encode the UIEvent (which is actually a UITouchesEvent). How can I extend the UIEvent or UIInternalEvent to make them encodable to NSData?
Methods I use for encoding/decoding:
-(NSString *)stringFromDictionary:(NSDictionary *)dict{
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:dict forKey:#"dictKey"];
[archiver finishEncoding];
return [Base64 encode:data];
}
-(NSDictionary *)dictionaryFromString:(NSString *)string{
NSData *data = [[NSMutableData alloc] initWithData:[Base64 decode:string]];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSDictionary *myDictionary = [unarchiver decodeObjectForKey:#"dictKey"];
[unarchiver finishDecoding];
return myDictionary;
}
Error I get:
-[UITouchesEvent encodeWithCoder:]: unrecognized selector sent to instance
Please let me know if I'm missing any important info regarding debugging. Thanks!
You have to have UITouch or UITouchesEvent adapt the UICoding protocol. This means it must support these methods:
- (id)initWithCoder:(NSCoder *)decoder;
- (void)encodeWithCoder:(NSCoder *)encoder;
I haven't tried this myself, but it should work if you do this in a class category. The difficulty will be to find out what you need to encode so it can be decoded again into a correct instance, if that's what you need.
I have a Checklist app and I want to save the name of the items and if they are checked or not.
Xcode tells me that my saved file has to be here:
2012-11-11 20:24:42.726 Checklists[1459:c07] Documents directory /Users/astanciu/Library/Application Support/iPhone Simulator/6.0/Applications/F4A90B71-A8CF-4EF8-B628-508C8B50CAD0/Library/Documentation
2012-11-11 20:24:42.728 Checklists[1459:c07] File path /Users/astanciu/Library/Application Support/iPhone Simulator/6.0/Applications/F4A90B71-A8CF-4EF8-B628-508C8B50CAD0/Library/Documentation/Checklists.plist
But in the Library folder I have only Caches and Preferences. Can't find anywhere the Checklists.plist file using Finder.
I am using Mountain Lion 10.8 and Xcode 4.5.2.
Code:
-(void)saveChecklistItems
{
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:items forKey:#"ChecklistItems"];
[archiver finishEncoding];
[data writeToFile:[self dataFilePath] atomically:YES];
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
if(self = [super initWithCoder:aDecoder])
{
[self loadChecklistItems];
}
return self;
}
-(void)loadChecklistItems
{
NSString *path = [self dataFilePath];
if([[NSFileManager defaultManager] fileExistsAtPath:path])
{
NSData *data = [[NSData alloc] initWithContentsOfFile:path];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
items = [unarchiver decodeObjectForKey:#"ChecklistItems"];
}
else
{
items = [[NSMutableArray alloc] initWithCapacity:20];
}
}
Anyone has an idea what is the problem here ?
Thanks.
You don't show the code for determining self dataFilePath. It appears to reference Library/Documentation which is a non-standard directory.
Either you meant to use the NSDocumentDirectory constant instead of NSDocumatationDirectory in the code you have to determine the path, or you need to ensure that the directory exists before you try to write to it.