How to encode and decode a const char * - objective-c

This is the first time for me to try to serialize/deserialize an object. One member of the object is a type of const char *.
----------------------------------------Added info here----------------------------------
This const char * is a byte array that represents an image. Because of image recognition requirements, I really need to get the values to the UI and back from the UI and store it as a byte array. I hope this clears up some confusion.
-------------------------------------------Ended here-------------------------------------
I am completely baffled by the options I have for both decoding and encoding. I cannot make sense out of which option to use and the only one that makes sense is asking for an at:(const void *)array. What goes there?
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if (self) {
Below is the line that I cannot figure out. I have the first two parameters alright but what is the third one? The parameter for at: is an array. But I don't have an array. Am I supposed to be decoding these to an array? If so, then I need to initialize an array to put the chars into?
_templateData = [decoder decodeArrayOfObjCType:_templateData count:_templateSize at:<#(void *)#>]; //const char *templateData;
After that it seems OK:
_templateSize = [decoder decodeIntegerForKey:#"templateSize"]; //NSUInteger templateSize;
_templateQuality = [decoder decodeIntForKey:#"templateQuality"]; //int templateQuality;
_templateLocation = [decoder decodeIntForKey:#"templateLocation"]; //int templateLocation;
}
return self;
}
And then there is encoding it.
- (void)encodeWithCoder:(NSCoder *)coder
{
Same problem here. I don't have an array. Do I need to initialize an array to put the chars into? Is that how this line is supposed to work?
[coder encodeArrayOfObjCType:_templateData count:_templateSize at:<#(const void *)#>]
The rest appears OK, again:
[coder encodeInteger:_templateSize forKey:#"templateSize"];
[coder encodeInt:_templateQuality forKey:#"templateQuality"];
[coder encodeInt:_templateLocation forKey:#"templateLocation"];
}
Has anyone out there successfully encoded and decoded a type that is a const char *?
Thank you for your help.

It sounds like the best solution is to turn your array of data into an NSData instance, and encode that. It's pretty straightforward:
NSData *data = [NSData dataWithBytesNoCopy:_templateData length:lengthOfTemplateData freeWhenDone:NO];
[coder encodeObject:data forKey:#"templateData"];
Then, for decoding:
NSData *data = [decoder decodeObjectForKey:#"templateData"];
[data getBytes:_templateData length:sizeOfTemplateDataBuffer]; // Assumes _templateData has been allocated to hold sizeOfTemplateDataBuffer bytes.

Related

Why is NSArray of NSNumber(s) failing to decode?

I have a custom class that conforms to NSSecureCoding. I'm trying to encode its data, write it to a file, and decode it. The class contains an int array which I convert to an NSArray filled with NSNumber elements before encoding. When I decode the array, decodeObjectForKey returns nil. Also, when I write the encoded array to file, the plist doesn't look right. For example, it doesn't have the "key_for_ns_array" key anywhere in its structure.
Here's a simplified version of my code:
#interface c_my_data_wrapper : NSObject <NSSecureCoding>
#property (assign, nonatomic) struct s_my_data my_data; // the array exists as an int[] in here
#end
#implementation c_my_data_wrapper
- (void)encodeWithCoder:(NSCoder *)encoder
{
NSMutableArray *ns_array= [NSMutableArray array];
int_array_to_ns_array(self.my_data.my_int_array, count, ns_array);
[encoder encodeObject:ns_array forKey:#"key_for_ns_array"];
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self= [self init];
if (self)
{
// this returns nil
NSArray *ns_array= [decoder decodeObjectForKey:#"key_for_ns_array"];
}
return self;
}
#end
c_secure_storage_data_wrapper* g_my_data_wrapper= nil; // allocation not shown
void store_data(struct s_storage_data_secure *new_data)
{
g_my_data_wrapper.my_data= *new_data;
NSData *encoded_data=
[NSKeyedArchiver archivedDataWithRootObject:g_my_data_wrapper
requiringSecureCoding:YES
error:nil];
// then I write the encoded data to a file
}
void load_data(void)
{
NSData *decoded_data= [NSData dataWithContentsOfURL:my_url];
g_my_data_wrapper=
[NSKeyedUnarchiver unarchivedObjectOfClass:[c_my_data_wrapper class]
fromData:decoded_data
error:nil];
}
void int_array_to_ns_array(
int *int_array,
int count,
NSMutableArray *out_ns_array)
{
for (int index= 0; index < count; ++index)
{
[out_ns_array addObject:[NSNumber numberWithInt:int_array[index]]];
}
}
If you're curious about the whole int array to ns array conversion thing... it's because I prefer to write in c++. I'm not very experienced with objective-c, so I only write it when I need to bridge the gap between my game and iOS. In this case, s_my_data is a type that's shared between the two.
One problem, probably the problem, is that you claim your class is NSSecureCodeing-compliant, but you're still calling -decodeObjectForKey: instead of -decodeObjectOfClass:forKey::
NSArray *ns_array= [decoder decodeObjectForKey:#"key_for_ns_array"];
That's not secure. The whole point of NSSecureCoding is that you tell the decoder what type you're expecting to get back, which is what -decodeObjectOfClass:forKey: does.

Equal methods does not compute same value

I'm trying to learn objective-c. I'm trying to parse a binary file doing the following (simplified):
#interface DatFile : NSObject {
NSData* _data;
}
-(id)initWithFilePath:(NSString *)filePath;
-(void) readFile;
-(void) auxiliaryMethod;
#implementation DatFile
- (id) initWithFilePath:(NSString *)filePath {
if ( self = [super init] ) {
_data = [NSData dataWithContentsOfFile:filePath];
}
return self;
}
-(void) readFile {
int header;
[_data getBytes: &header range: NSMakeRange(0, 4)];
NSLog(#"header: %u", header);
short key;
[_data getBytes: &key range: NSMakeRange(4, 2)];
NSLog(#"key: %u", key);
short value;
[_data getBytes: &value range: NSMakeRange(6, 1)];
NSLog(#"value: %u", value);
[self auxiliaryMethod];
}
-(void) auxiliaryMethod {
short value;
[_data getBytes: &value range: NSMakeRange(6, 1)];
NSLog(#"value: %u", value);
}
My problem is that the code inside the auxiliaryMethod does not compute the same value computed by readFile method. Since the _data object is the same, why the method are computing different values? And, as you can see, the logic inside the auxiliaryMethod is just a copy of the other one...
In other languages (java) I usually separate some logic in smaller methods in order to make the code mode readable/maintainable. This is why I'm trying to mimic it with ObjC.
Of course that probably missing something, but after some hours, I gave up. I don't see where is my mistake. Probably I should erase my project and start it again...
TIA,
Bob
%u is for printing a 32 bit unsigned value. A short is 16 bits. Therefore, you are printing the parsed value plus some stack garbage.
Try %hu; the h modifies %u to print a short value.
If that doesn't work:
make sure your data objects are the same between the two methods
(don't see how they can't be... but...)
show the actual values printed
decode into an unsigned long that is explicitly initialized to 0 before decoding
With this kind of bug, it is pretty much guaranteed it is either that you aren't dealing with the input you think you are or you are dealing with some C-ism related to data types and the implied conversions in this kind of code.
I.e. assuming _data is consistent, then it indicates that the code you think is working is not actually working, but only appearing to by coincidence.
The problem is that you're passing a range of size 1 to getBytes:range:, but a short is 2 bytes in size. So getBytes:range: is only setting one of value's bytes, and the other contains random garbage.
If you really only want to get one byte from the data, change the type of value to int8_t. If you want to get two bytes, pass 2 as the second argument of NSMakeRange (and I recommend also changing the type of value to int16_t).

How to change this so that it returns arrays

The following code works perfectly and shows the correct output:
- (void)viewDidLoad {
[super viewDidLoad];
[self expand_combinations:#"abcd" arg2:#"" arg3:3];
}
-(void) expand_combinations: (NSString *) remaining_string arg2:(NSString *)s arg3:(int) remain_depth
{
if(remain_depth==0)
{
printf("%s\n",[s UTF8String]);
return;
}
NSString *str = [[NSString alloc] initWithString:s];
for(int k=0; k < [remaining_string length]; ++k)
{
str = [s stringByAppendingString:[[remaining_string substringFromIndex:k] substringToIndex:1]];
[self expand_combinations:[remaining_string substringFromIndex:k+1] arg2:str arg3:remain_depth - 1];
}
return;
}
However, instead of outputting the results, I want to return them to an NSArray. How can this code be changed to do that? I need to use the information that this function generates in other parts of my program.
There are several things that you need to change in your code.
First - consider changing the name of your method to something more legible and meaningful than -expand_combinations:arg2:arg3.
Second - you have a memory leak. You don't need to set allocate memory and initialize str with the string s, because you change its value right away in the loop without releasing the old value.
Third - take a look at NSMutableArray. At the beginning of the method, create an array with [NSMutableArray array], and at every line that you have printf, instead, add the string to the array. Then return it.
basicaly you have:
create mutable array in viewDidLoad before [self expand_combinations ...
add aditional parameter (mutable array) to expand_combinations
populate array in expand_combinations

Return NSString from a recursive function

I have a recursive function that is designed to take parse a tree and store all the values of the tree nodes in an NSString.
Is the algorithm below correct?
NSString* finalString = [self parseTree:rootNode string:#""];
-(NSString*)parseTree:(Node*)currentNode string:(NSMutableString*)myString
{
[myString appendText:currentNode.value];
for(int i=0;i<[currentNode.children length];i++){
return [self parseTree:[currentNode.children] objectAtIndex:i] string:myString];
}
}
No, it is not.
You pass in #"" as the starting string. However, #"" is not an NSMutableString. This will certainly produce an exception when run.
As soon as you return, then that method stops and you don't execute any more. This means that you go through the first iteration of the loop, you'll stop. Forever.
You don't return anything if there are no children in the currentNode.
There's no such method as appendText:. You probably mean appendString:
Here's another question: Why do you need to return a value at all? You're passing in an NSMutableString and modifying it, so why not just always modify it in place and not bother with a return value? For example:
- (void) parseTree:(Node*)currentNode string:(NSMutableString*)myString {
[myString appendString:currentNode.value];
for(Node * child in [currentNode children]){
[self parseTree:child string:myString];
}
}
And then invoke this with:
NSMutableString * finalString = [NSMutableString string];
[self parseTree:aNode string:finalString];

How to encode/decode a CFUUIDRef in Objective-C

I want to have a GUID in my objective-c model to act as a unique id. My problem is how to save the CFUUIDRef with my NSCoder as its not a an Object type.
I keep playing around with the following lines to encode/decode but I can't seem to find any good examples of how to save struct types in objective-c (all of my NSObject types are encoding/decoding fine).
e.g. for encoding I am trying (which I think looks good?):
CFUUIDBytes bytes = CFUUIDGetUUIDBytes(uuid);
eencoder encodeBytes: &bytes length: sizeof(bytes)];
and for decoding which is where I get more stuck:
NSUInteger blockSize;
const void* bytes = [decoder decodeBytesForKey: kFieldCreatedKey returnedLength:&blockSize];
if(blockSize > 0) {
uuid = CFUUIDCreateFromUUIDBytes(NULL, (CFUUIDBytes)bytes);
}
I gt an error "conversion to a non-scaler type" above - I've tried several incarnations from bits of code I've seen on the web. Can anyone point me in the right direction?
Tim
The easier (but a bit more inefficient) way is to store it as an NSString (CFString) using CFUUIDCreateString, and recover the UUID with CFUUIDCreateFromString.
The problem with your code is the last line of the decoding, "bytes" is a pointer to a CFUUIDBytes struct and you're trying to cast it as if it is the CFUUIDBytes struct itself, which is not correct and is correctly detected by the compiler. Try changing the last line to:
uuid = CFUUIDCreateFromUUIDBytes(NULL, *((CFUUIDBytes*)bytes));
The idea here is that you cast "bytes" to be a pointer to CFUUIDBytes (inner brackets) and then dereference the casted pointer (outer brackets). The outer brackets are not strictly necessary but I use them to make the expression clearer.
Based on the answers given, I tried the casting approach given by Eyal and also the NSData approach suggested by Rob, and I thought that the latter seemed clearer, although I am interested in what others think.
I ended up with the following:
- (void)encodeWithCoder:(NSCoder *)encoder {
// other fields encoded here
CFUUIDBytes bytes = CFUUIDGetUUIDBytes(uuid);
NSData* data = [NSData dataWithBytes: &bytes length: sizeof(bytes)];
[encoder encodeObject: data forKey: kFieldUUIDKey];
}
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
// other fields unencoded here
NSData* data = [decoder decodeObjectForKey: kFieldUUIDKey];
if(data) {
CFUUIDBytes uuidBytes;
[data getBytes: &uuidBytes];
uuid = CFUUIDCreateFromUUIDBytes(NULL,uuidBytes);
} else {
uuid = CFUUIDCreate(NULL);
}
}
}