Extracting unknown data from plist - ios

I'm writing an iOS loader that loads data from a plist intending to send vertex data, etc. to the GPU via OpenGL. I can easily extract objects of standard types, like strings, integers, etc.
Where I get stumped is when I encounter what appears to be raw data as a dictionary object. The plist is a native file saved by my 3D modeling software, of which I'm not the author, so I don't know how the data was written into this object.
Some things I DO know about the object, it's likely an array of floats, each vertex needs a float value for X, Y, and Z, and there are 26 vertices in the example below.
Here's the actual data object in the plist file:
<key>vertex</key>
<data>
AAAAAL8AAAAAAAAAAAAAAAAAAAC/AAAAPwAAAAAAAAA+gAAAvwAA
AD7ds9cAAAAAPt2z2L8AAAA+f///AAAAAD8AAAC/AAAAsru9LgAA
AAA+3bPXvwAAAL6AAAEAAAAAPoAAAb8AAAC+3bPXAAAAALM7vS6/
AAAAvwAAAAAAAAC+gAADvwAAAL7ds9UAAAAAvt2z2L8AAAC+f//9
AAAAAL8AAAC/AAAAMczeLgAAAAC+3bPYvwAAAD5///0AAAAAvn//
+L8AAAA+3bPaAAAAAD6AAAA/AAAAPt2z1wAAAAAAAAAAPwAAAD8A
AAAAAAAAAAAAAD8AAAAAAAAAAAAAAD7ds9g/AAAAPn///wAAAAA/
AAAAPwAAALK7vS4AAAAAPt2z1z8AAAC+gAABAAAAAD6AAAE/AAAA
vt2z1wAAAACzO70uPwAAAL8AAAAAAAAAvoAAAz8AAAC+3bPVAAAA
AL7ds9g/AAAAvn///QAAAAC/AAAAPwAAADHM3i4AAAAAvt2z2D8A
AAA+f//9AAAAAL5///g/AAAAPt2z2gAAAAA=
</data>
Any ideas about how to read this? Here's where I am:
// get plist
NSString *path = [[NSBundle mainBundle] pathForResource:#"Cylinder" ofType:#"jas"];
NSDictionary *cheetahFile = [NSDictionary dictionaryWithContentsOfFile:path];
NSArray *objectArray = [cheetahFile objectForKey:#"Objects"];
NSDictionary *model = [objectArray objectAtIndex:1];
//get vertex count
GLshort vertCount = [[model valueForKey:#"vertexcount"] intValue];
//All good so far...but...
//get vertex data?... this doesn't work:
NSMutableArray *vertArray = [NSMutableArray arrayWithObject:[model objectForKey:#"vertex"]];
P.S. Sorry in advance if I'm making a rookie mistake. I'm a designer by profession, not a programmer. So talk slow using soothing tones while I eat my crayons. :)

The <data> part is an encoded NSData object. You can do this:
NSData *vertexData = model[#"vertex"];
What you do with that data is a whole other discussion.

Related

iOS: Read data from .plist in dictionary

I would like to retrieve data from a plist in my .app directory.
I can not figure out how to get sub-dictionary data. For instance, I would like to get the MemoriesDictionary/Memory1/Event1/EventName value.
I am able to get the MemoryCount value into iMemCount just fine with:
int iMemCount;
//Do file searching/getting for plist
NSString *plistDirectory = [[NSBundle mainBundle] pathForResource:#"memoryDetails" ofType:#"plist"];
NSLog(#"array: %#",plistDirectory);
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath: plistDirectory]) //4
{
NSLog(#"exists");
NSMutableDictionary *savedStock = [[NSMutableDictionary alloc] initWithContentsOfFile: plistDirectory];
iMemCount = [[savedStock objectForKey:#"MemoryCount"] intValue];
} else {
NSLog(#"Does Not Exist");
iMemCount = 0;
}
NSString *string = savedStock[#"MemoriesDictionary"][#"Memory1"][#"Event1"][#"EventName"];
Edit
You could also rewrite:
int iMemCount;
iMemCount = [[savedStock objectForKey:#"MemoryCount"] intValue];
as
NSInteger iMemCount;
iMemCount = [savedStock[#"MemoryCount"] integerValue];
You really should be taking into account the possibility of your iOS code running on a 64-bit processor and use the appropriate platform safe types (NSInteger, CGFloat, CFIndex, etc)
Note also that using an NSMutableDictionary here may not do what you expect: the top-level dictionary will be mutable but all the sub-objects (arrays, dictionaries) will be immutable and throw an exception if you try to access them.
In general I’d caution against doing lookups several levels deep in dictionaries, because it’s usually a sign that you’re doing something the hard way. The pattern I like to follow is create classes that can read and write themselves to dictionaries, and then when I read in a file create instances that can be queried directly.
Dealing with a bunch of mutable dictionaries with a bunch of string keys is a recipe for heartache and disaster. You lose compile-time type checking and compile-time variable name checking and readability.
Also, I don’t know if this is a contrived example file, but I wouldn’t write the count to the file explicitly—just calculate it as needed. Duplicating data leads to data being out of sync. And it seems like MemoriesDictionary really wants to be an array, if the names of the memories are inside the sub-dictionaries, and the keys are used to keep the memories in order.

iOS - Issue with reading from a plist

I'm having an issue with reading from a plist I can't seem to figure.
My plist list looks like this (note i've simplified it for examples sake):
Then I'm reading code like this:
NSString *path = [[NSBundle mainBundle] pathForResource:#"ContactDetails" ofType:#"plist"];
NSMutableDictionary *myDictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
NSArray* allmyKeys = [myDictionary allKeysForObject:#"Name1"];
Any ideas - the issue i keep facing is allmyKeys shows up as containing 0 objects. On debugging myDictionary is correctly populated so not sure why it doesn't work.
Many Thanks
The problem is with this line:
NSArray* allmyKeys = [myDictionary allKeysForObject:#"Name1"];
#"Name1" is a key in your dictionary, not a value. Even if it were a value, the string #"Name1" is a different object from the string with the same value in myDictionary, so this call would not do what you expect.
You probably want to access the dictionary by doing
NSDictionary *userDetails = myDictionary[#"Name1"];

How do I access random values from an array nested in a dictionary from plist file?

Basically I need to get a random letter and the points associated with that letter from a plist.
I'm honestly not sure I have my plist set up in the best possible way to do this, I've never worked with plist before:
As soon as I get this working I'm going to be adding C-Z into the plist with each letters associated points. I'm just created a basic word tile game to try and learn sprite kit.
So far I've been able to access the plist file but have not had any luck getting a random Letter and it's Points.I'm getting all kinds of errors with everything I try.
Here's my code so far to access the plist:
NSString* fileName = #"letters.plist";
NSString* filepath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:fileName];
NSDictionary* lettersPlist = [NSDictionary dictionaryWithContentsOfFile:filepath];
NSDictionary* letters = lettersPlist[#"letters"];
So how do I get a random letter and it's points from this plist? And is there a better way to do this?
Just get a random number between 0 and the number of items in your letters array, and take that info out. For example, continuing your code:
u_int32_t bound = (u_int32_t)[letters count];
NSDictionary* randomLetterInfo = letters[arc4random_uniform(bound)];
NSString* randomLetter = randomLetterInfo[#"Letter"];
NSString* points = randomLetterInfo[#"Points"]; // points are a string in your plist

NSMutuableArray & Core Data not behaving properly

I have string of photo urls, pertaining to their location (e.x.: googleusercontent.com/photo.gif). I saved that single string (the string maybe holds say, four other urls) to Core Data using the 'binary data' type. When I retrieve the urls from core data, it displays the amount of strings correctly, but does not display the correct data outside of the for loop.
// Loop through the photo selection to get the urls
for (int i=0; i < self.tempPhotos.count; i++)
{
self.photo = [ self.tempPhotos objectAtIndex:i];
self.image = [ photo originalImage];
NSString *urls = [image.URL absoluteString]; // <-- here we get the urls and store
self.selected_urls = urls;
NSData * data = [NSKeyedArchiver archivedDataWithRootObject:urls];
self.group.selectedurl = data; // assign it to the core data object
}
// Now we're out of the for loop, here is where it will not retrieve and log properly. if i put this inside the for loop, it will. why is that?
NSMutableArray *temp = [NSMutableArray *)[NSKeyedUnarchiver unarchiveObjectWithData:self.group.selectedurl];
NSLog(#"%#",temp);
If I put the NSLog inside the for loop, I get it correctly. It should look something like this:
- "googleusercontent.com/photo1.gif"
- "googleusercontent.com/photo2.gif"
When I retrieve the Core Data object outside of the for loop, it gives me this:
- "googleusercontent.com/photo1.gif"
- "googleusercontent.com/photo1.gif"
I have no clue why it is working inside the for loop, but it won't work properly anywhere outside, and I feel like I am missing an obvious step. Could I have any help?
Your urls variable is an NSString, which you're archiving and then later unarchiving as an NSMutableArray, which is incorrect. I think what you're trying to do is build an NSMutableArray of NSStrings in your for loop:
NSMutableArray *urls = [NSMutableArray arrayWithCapacity:self.tempPhotos.count];
for (int i=0; i < self.tempPhotos.count; i++)
{
...
[urls addObject:[image.URL absoluteString]];
...
NSData * data = [NSKeyedArchiver archivedDataWithRootObject:urls];
...
}

Efficient way of compressing NSDictionary into NSData

I am using BTLE to write data into one of the characteristics of peripheral. I wanted to send an NSDictionary into that characteristics. Since there is a limitation of 130 bytes of data being sent over BTLE, I want an efficient way of compressing NSDictionary into NSData and then send across. I am using below piece of code which is exceeding limit. Any ideas?
NSDictionary *aDict = #{ #"Value1": #"sadsadasdasdsadqwwqsadasd",
#"Value2": #"10",
#"Value3": #"12" };
NSData *aData = [NSKeyedArchiver archivedDataWithRootObject:aDict];
NSLog(#"Data Size = %#",
[NSByteCountFormatter stringFromByteCount:aData.length
countStyle:NSByteCountFormatterCountStyleFile]);
I don't think trying to use any form of compression will be effective, or even an improvement at all at this scale, because all compression algorithms work best when they have a lot of data to work with, and hence many duplicates and patterns to find. When your entire data size is 130 bytes, any form of zip compression isn't really a viable option.
If your dictionary will only contain property-list values (arrays, dictionaries, strings, numbers), then you can use JSON serialisation instead of NSKeyedArchiver:
NSData *JSONData = [NSJSONSerialization dataWithJSONObject:anObject
options:0
error:nil];
This immediately makes the output data much shorter in your case:
NSDictionary *aDict = #{ #"Value1": #"sadsadasdasdsadqwwqsadasd",
#"Value2": #"10",
#"Value3": #"12" };
NSData *aData = [NSKeyedArchiver archivedDataWithRootObject:aDict];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:aDict
options:0
error:nil];
NSLog(#"NSKeyedArchiver Data Size = %#, JSON Data Size = %#",
[NSByteCountFormatter stringFromByteCount:aData.length
countStyle:NSByteCountFormatterCountStyleFile],
[NSByteCountFormatter stringFromByteCount:jsonData.length
countStyle:NSByteCountFormatterCountStyleFile]
);
NSKeyedArchiver Data Size = 380 bytes, JSON Data Size = 66 bytes
As you can see, the JSON serialised data is almost 6 times smaller than the NSKeyedArchiver serialised data, and fits easily in your 130 byte limit. And the best thing is, it's only one line of code.
UPDATE: Just to rub it in some more :), here is the data that NSKeyedArchiver produces (added as image because it contains a lot of "illegal" characters that I couldn't copy and paste):
As you can see, it contains a lot of useless data that you don't really need (highlighted blue), that's basically just to give NSKeyedUnarchiver enough information to be able to unarchive it later.
Now, let's look at the JSON data:
{"Value3":"12","Value2":"10","Value1":"sadsadasdasdsadqwwqsadasd"}
That's it. One line. 66 bytes. Of those, 19 bytes aren't your values. In other words, 71% of that JSON data is your values, and the rest is markup, so to speak. Meanwhile, in the NSKeyedArchiver data, your values make up, wait for it, 12% of the result. I think you can clearly see which one is more efficient for storage here.

Resources