0

I need a little help with decodeBytesForKey using NSKeyedUnarchiver unarchiveObjectWithFile. I appear to be writing everything correctly but reading the data gives me a EXC_BAD_ACCESS error. I'm trying to deserialize data and have gotten confused by the various answers and examples out there. Can someone please point out what I am doing wrong?

for (NSString *item in directoryContent){
    if ([[item pathExtension] isEqualToString:@"template"]) {

        **// Next line is the problem line from the calling code:**
        templateToRead = [[NSKeyedUnarchiver unarchiveObjectWithFile:[documentsDirectory stringByAppendingPathComponent:item]] mutableCopy];

        IDImageTemplate *template = [[IDImageTemplate alloc] init];
        template.templateData = templateToRead.templateData;
        template.templateQuality = templateToRead.templateQuality;
        template.templateSize = templateToRead.templateSize;
        template.templateLocation = templateToRead.templateLocation;
        [templatesRead addTemplate:template];
    }
}



- (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeInteger:self.templateSize forKey:@"templateSize"];
    [encoder encodeBytes:(const unsigned char*)self.templateData length:self.templateSize forKey:@"templateData"];
    [encoder encodeInt:self.templateQuality forKey:@"templateQuality"];
    [encoder encodeInt:self.templateLocation forKey:@"templateLocation"];
}

- (id)initWithCoder:(NSCoder *)decoder {
    self = [super init];
    if (self) {
        self.templateSize = [decoder decodeIntegerForKey:@"templateSize"];

        **// Next line is where the real problem is:**

        self.templateData = (const char *)[decoder decodeBytesForKey:@"templateData" returnedLength:(NSUInteger *)self.templateSize];        
        self.templateQuality = [decoder decodeIntForKey:@"templateQuality"];
        self.templateLocation = [decoder decodeIntForKey:@"templateLocation"];
    }
    return self;
}

If I comment out the encoder and decoder lines for the data, I get the correct values back for everything else, but I need the data too.

Patricia
  • 5,019
  • 14
  • 72
  • 152

1 Answers1

1

Ok, there are a few different issues here. Thankfully, they're mostly easy to fix if you know C. (As an aside, you should probably spend some time [re-]learning C, since I get the impression that you've so far just gotten by on the basics of Objective-C and haven't learned much about the language it's a superset of.) Anyway, all but one of these has to do with a specific line, so I'll copy it here and chop it down so it's easier to inspect:

self.templateData =
    (const char *)[decoder
                   decodeBytesForKey:@"templateData"
                   returnedLength:(NSUInteger *)self.templateSize];
  1. Although there's a more glaring issue, the one that's likely causing this particularly crash is this: (NSUInteger *)self.templateSize. I'd guess you were trying to get the address of the templateSize property's instance variable, but this won't work. What you'll actually need to do if you want to pass in the address of that instance variable is something like &_instanceVariable or &this->_instanceVariable (the former usually works since Obj-C allows unqualified instance variable access). (Note: the unary & in the last two code bits is the address-of operator.)

    The code you've written is wrong because it's not getting the address of that property's underlying instance variable, it's just casting the returned size to a pointer. So if it returns 0, the pointer is 0; if it returns 4, the pointer is 4; if it returns 42801, the pointer is 42801 (i.e., it's just pointing to whatever those would point to, the first being NULL). So, in the end, decodeBytesForKey:returnedLength: is attempting to write to an address that may or may not be usable memory. It's an easy thing to fix, thankfully.

  2. You could fix #1 and this alone should get it technically working for a short amount of time, but it's still wrong. You also have to take into account that the buffer returned by decodeBytesForKey:returnedLength: is a temporary buffer — it lives only as long as the coder lives. This is mentioned in the discussion segment of decodeBytesForKey:returnedLength:'s docs for NSKeyedUnarchiver and in the NSCoder decodeBytesWithReturnedLength: documentation. Unfortunately, if you're not looking in exactly the right place, that detail might be easy to miss, but it returning a const pointer might be evidence that the result had a temporary lifespan. Anyway, You need to make a copy of the returned buffer if you want to keep that data.

    You mentioned in chat that the templateData property is itself a char *, so this means you're going to probably want to allocate a new block of memory of the same size, memcpy the buffer, and store that. This obviously goes with the assumption that you know to also free the buffer.

    The easier way to do this, by far, is to just use NSData instead of a pointer to some memory. You can still encode as bytes (which allows you to give it a key) and decode as bytes, but you can get NSData to handle copying the buffer and freeing it for you. E.g.,

    NSUInteger size = 0;
    const char *buffer = [coder decodeBytesForKey:@"key" returnedLength:&size];
    NSData *data = [NSData dataWithBytes:buffer length:size];
    

    And with that, the NSData object and ARC in turn handle that for you. Depending on your specific needs, this might not be preferable, so obviously determine what's important for your specific case.

  3. This is not a specific issue but more of a redundancy thing: you do not have to encode the length of the bytes before encoding the bytes themselves. This is handled by the coder already, hence why it returns its length via the returnedLength: argument (lengthp). So, the lines for encoding and decoding the length are unneeded.

  • OK. Let me work on this. I'm sure you are 100% correct. Just before getting this answer I got past the one line by Xcode prompting me to fix the pointer to &. From all I've read, I've been on this track but just wasn't getting it. Yes, I'm a noob with C. Thanks for the help and I'll mark it the answer in just a few. – Patricia Jun 16 '14 at 21:32
  • OK. We are trying to write out a class to a file. The class contains four properties: templateData (which ultimately needs to be a char * to match an external call), int templateSize (which is the length of the data, NSUInteger templateQuality, and int templateLocation. Part of my confusion was that I didn't realize returnedLength means "returned" length. LOL! Silly me. – Patricia Jun 16 '14 at 22:17
  • Happens — nobody's perfect. –  Jun 16 '14 at 22:57