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.
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];
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.
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.
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.
Related
I've spent some serious time debugging this problem and I am stumped.
I'm trying to pack custom objects tightly into NSData to save space when they are saved to disk. I have implemented custom encoding and decoding methods for these custom objects. When I pack and unpack the objects, everything is initialized properly according to the debugger. Immediately after the code runs, it has either an EXC_BAD_ACCESS code=1 or code=2.
Here is the encoding and decoding:
-(instancetype) initWithData:(NSData *)data {
self = [super init];
if (self) {
_memConstants = [[DWMemoryConstants alloc]init];
int arraySize = _memConstants.chunkSize * _memConstants.chunkSize;
NSData* unencodedBlocks[arraySize];
[data getBytes:unencodedBlocks];
_blocks = [[NSMutableDictionary alloc]init];
for (int i = 0; i < 10; i++) {
DWBlock *block = [[DWBlock alloc]initWithData:unencodedBlocks[i]];
[_blocks setObject:block forKey:[NSValue valueWithCGPoint:CGPointFromString(block.blockName)]];
if (i == 0) {
_position = CGPointFromString(block.chunkName);
}
}
_chunkName = NSStringFromCGPoint(_position);
_bounds = CGRectMake(_position.x * _memConstants.chunkSize * kBlockSpriteWidth,
_position.y * _memConstants.chunkSize * kBlockSpriteWidth,
_memConstants.chunkSize * kBlockSpriteWidth,
_memConstants.chunkSize * kBlockSpriteWidth);
}
return self;
}
-(NSData *) customEncode {
NSMutableData *data = [[NSMutableData alloc]init];
NSData* blocks[_blocks.allValues.count];
int count = 0;
for (DWBlock *block in _blocks.allValues) {
blocks[count] = [block customEncode];
count++;
}
[data appendBytes:&blocks length:sizeof(blocks)];
return data;
}
The exception happens at the end of this code. It successfully prints all of the log messages first, and I have verified that there is nothing wrong with the Chunk object:
-(void) testEncodeDecode {
DWChunk *testChunk = [[DWChunk alloc]initWithPosition:CGPointMake(100, 100)];
NSData *testData = [testChunk customEncode];
NSLog(#"datasize= %#", testData);
DWChunk *unencodedChunk = [[DWChunk alloc]initWithData:testData];
NSLog(#"blocks=%#", unencodedChunk.blocks.allValues);
NSLog(#"this message will print");
}
As I said, all of the variables for the Chunk and Block objects are properly initialized. I suspect that the problem is related to improper releasing of objects, but I don't know where to go from here.
Edit: I have enabled checking for zombies, and now I get this error message:
2014-10-24 11:38:31.775 DigWorld[10622:60b] *** -[NSConcreteMutableData release]: message sent to deallocated instance 0x81790710
EDIT:
My initial thoughts where technically incorrect (read the code too fast...), but the actual error is along the exact same lines. By saving blocks, you save only the array of pointers, and not the actual data. You can make sure of that: in your comment you say that sizeof(blocks)=3600 and _blocks.allValues.count=900, so you only save the 900 pointers (* 4 bytes/pointer = 3600 bytes)
The actual data as created by [block customEncode] is then disposed of by ARC.
When reading this array afterward, you get pointers to pieces of memory (formerly NSData) that have been marked as freed, producing your crash, and explaining the error message when enabling zombies.
The tricky part: since this is the same instance of the program, and encode and decode are run next to each other, the actual memory has not been reallocated/overwritten. So you get the impression the objects are correctly saved. But rest assured this memory will eventually be overwritten by something else. You can make sure of that by saving the NSData on disk and decoding it on a re-run of the program.
It seems to me that you are trying to reinvent the wheel here.
The NSCoding protocol has been created exactly for that purpose: persisting graphs of objects on disk. The NSKeyedArchiver class will give you serialisation to disk with flexibility, and platform-independance, in a well-tested and documented interface.
You should be leveraging that architecture. Even if you decide that NSKeyedArchiver creates archives too "big" for your purposes, use the architecture in place and create your own archiver.
If you choose to do so, you can get inspiration with this open source NSCoder subclass implementation (disclaimer: I wrote it)
I'm having trouble removing items from my NSMutableArray. I'm extremely new to Objective-C, so please bear with me.
So far, I have the following: I'm trying to remove a line from the array if it has certain text inside. I cannot do this while fast-enumerating, so I'm trying to store the index, for removal after the enumeration has finished. However, I'm being told that this makes a pointer from an integer without a cast. Confused!
//First remove any previous Offending entry.
//Read hostfile into array.
NSString *hostFileOriginalString = [[NSString alloc] initWithContentsOfFile:#"/etc/hosts"];
NSMutableArray *hostFileOriginalArray = [[hostFileOriginalString componentsSeparatedByString:#"\n"] mutableCopy];
NSInteger hostFileOffendingLocation;
//Use a for each loop to iterate through the array.
for (NSString *lineOfHosts in hostFileOriginalArray) {
if ([lineOfHosts rangeOfString:#"Offending"].location != NSNotFound) {
//Offending entry found, so remove it.
//[hostFileOriginalArray removeObject:lineOfHosts];
hostFileOffendingLocation = [hostFileOriginalArray indexOfObject:lineOfHosts];
//NSLog(#"%#", hostFileOffendingLocation);
}
}
//Release the Array.
[hostFileOriginalArray release];
//Remove offending entry from Array.
[hostFileOriginalArray removeObject:hostFileOffendingLocation];
My real question is why are you releasing your array before modifying it
try moving
[hostFileOriginalArray release];
to after
[hostFileOriginalArray removeObject:hostFileOffendingLocation];
You can do this without the loop by calling [hostFileOriginalArray removeObjectIdenticalTo:#"Offending"];
Note that it will remove multiple instances of the offending object, but that looks like what you want anyway. It will also do the operation in a fast way, without you having to worry about the implementation detail of which loop to use.
As a general rule (especially with the really commonly used objects like containers and NSString), check the class reference to see if Apple already has a way of doing what you want to do. It makes your code more readable to other Cocoa users (including future you), and reduces code maintenance- you're now leaving it up to Apple to add new technologies like Fast Enumeration to their code, and you get it for free when you link against new versions of the SDK.
Also, you should probably return hostFileOriginalArray at the end of the function, so it can do something useful- you can return it as an autoreleased object.
//Remove offending entry from Array.
[hostFileOriginalArray removeObjectAtIndex:hostFileOffendingLocation];
//Release the Array.
[hostFileOriginalArray release];
you should have been getting compiler warnings... take a look at them, they are usually very helpful, I always try to have 0 warnings... that way I know where I have done something careless.
I'm attempting to narrow down a bug to a minimum reproducible case and found something odd.
Consider this code:
static NSString *staticString = nil;
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
if (staticString == nil) {
staticString = [[NSArray arrayWithObjects:#"1", #"2", #"3", nil] componentsJoinedByString:#","];
}
[pool drain];
NSLog(#"static: %#", staticString);
return 0;
}
I'm expecting this code to crash. Instead, it logs:
2011-01-18 14:41:06.311 EmptyFoundation[61419:a0f] static: static:
However, if I change the NSLog() to:
NSLog(#"static: %s", [staticString UTF8String]);
Then it does crash.
edit a bit more info:
After draining the pool:
NSLog(#"static: %#", staticString); //this logs "static: static: "
NSLog(#"static: %#", [staticString description]); //this crashes
So apparently invoking a method on the string is good enough to get it to crash. In that case, why doesn't logging the string directly cause it to crash? Shouldn't NSLog() be invoking the -description method?
Where is the second "static: " coming from? Why isn't this crashing?
Results:
Both Kevin Ballard and Graham Lee are correct. Graham's correct in realizing that NSLog() is not invoking -description (as I was erroneously assuming), and Kevin is almost definitely correct that this is a weird stack-related issue with copying a format string and a va_list around.
NSLogging and NSString does not invoke -description. Graham elegantly showed this, and if you trace through the Core Foundation sources that do the logging, you'll see that this is the case. Any backtrace originating inside NSLog shows that it invokes NSLogv => _CFLogvEx => _CFStringCreateWithFormatAndArgumentsAux => _CFStringAppendFormatAndArgumentsAux. _CFStringAppendFormatAndArgumentsAux() (line 5365) is where all of the magic is going on. You can see that it's manually going through to find all the % substitutions. It only ends up invoking the description copy function if the type of the substitution is a CFFormatObjectType, the description function is non-nil, and the substitution hasn't already been handled by another type. Since we've shown that the description is not getting copied, it's reasonable to assume that an NSString gets handled earlier (in which case it's probably going to be doing a raw byte copy), which leads us to believe...
There's a stack error going on here, as Kevin surmises. Somehow the pointer that was pointing to the autoreleased string is getting substituted to a different object, which happens to be an NSString. So, it doesn't crash. Weird. However, if we change the type of the static variable to something else, like an NSArray, then the -description method does get called, and the program does crash as expected.
How truly and utterly strange. Points go to Kevin for being the most correct about the root cause of the behavior, and kudos to Graham for correcting my fallacious thinking. I wish I could accept two answers...
My best guess for what you're seeing is that NSLog() copies the format string (probably as a mutable copy), and then parses the arguments. Since you've dealloc'd staticString, it just so happens that the copy of the format string is being placed into the same location. This is causing you to see the "static: static: " output that you described. Of course, this behavior is undefined - there's no guarantee it will always use the same memory location for this.
On the other hand, your NSLog(#"static: %s", [staticString UTF8String]) is accessing staticString before the format string copy happens, which means it's accessing garbage memory.
Your assumption that NSLog() calls -description on an NSString instance is faulty. I just added this category:
#implementation NSString (GLDescription)
- (NSString *)description {
NSLog(#"-description called on %#", self);
return self;
}
#end
It doesn't cause a stack overflow, because it doesn't get called recursively. Not only that, but if I insert that category into the code in your question, I find this output:
2011-01-18 23:04:11.653 LogString[3769:a0f] -description called on 1
2011-01-18 23:04:11.656 LogString[3769:a0f] -description called on 2
2011-01-18 23:04:11.657 LogString[3769:a0f] -description called on 3
2011-01-18 23:04:11.658 LogString[3769:a0f] static: static:
so we conclude that NSLog() doesn't call -description on an NSString it comes across in its args. Why you get the static string twice is likely a quirk of the data on the stack when you erroneously access the released staticString variable.
Accessing dealocated memory does not necessarily cause a crash. The behavior is undefined. You are expecting too much!
Maybe it has something to do with the #"static:" being stored in the same memory location as staticString. staticString will be deallocated and it stores the #"static: %#" in that recycled mem location, so then the staticString pointer is on "static: %#" so it ends up static: static:.
This is a case of "Use after free()". What happens is "undefined behavior". Your example is really no different than:
char *stringPtr = NULL;
stringPtr = malloc(1024); // Example code, assumes this returns non-NULL.
strcpy(stringPtr, "Zippers!");
free(stringPtr);
printf("Pants: %s\n", stringPtr);
What happens at the printf line? Who knows. Anything from Pants: Zippers! to Pants: (...garbage...) Core Dump.
All the Objective-C specific stuff is actually irrelevant- it's the fact that you're using a pointer to memory which is no longer valid is the only thing that matters. You're better off throwing darts at the wall than trying to explain "why" it's not crashing and printing static: static. For performance reasons, most malloc implementations don't bother "reaping" free()'d allocations until they have to. IMHO, this is probably why your example isn't crashing in the way you were expecting it to.
If you really want to see this particular program crash, you can do one of the following:
Set the environment variable CFZombieLevel to 17 (scribble + don't free).
Set the environment variable NSZombieEnabled to YES.
Set the environment variable DYLD_INSERT_LIBRARIES to /usr/lib/libgmalloc.dylib (see man libgmalloc).
- (void)createAString:(NSString **)str
{
*str = [NSString stringWithString:#"Hi all!"];
[*str autorelease]; // ???? is this right ?
}
How should I use release or autorelease ? I don't want to release outside of the function of course :)
...
NSString *createStr;
[self createAString:&createStr];
NSLog(#"%#", createStr);
You're correct that you'd generally want to return autoreleased (or the like) objects from out params when you use this form. Your assignment statement in the function that sets *str to a string:
*str = [NSString stringWithString:#"foo"];
is already doing the right thing, because that method returns an instance of NSString that the caller doesn't own. Just like you could return this string object from your function without any further memory management, you can set it as the outparam as you've done. Your second snippet showing the call site is fine.
This said, I'm worried about a few things in your code that you should be sure you understand:
The value of str inside the method is still a **, and sending that a message (as you've done for the speculative autorelease) is nonsense. Be sure you fully understand doubly indirected pointers before using them too liberally. :) If you need to send str a message after creating it, send it to *str, which is what contains the NSString *.
Setting an outparam like this when the function returns void is not idiomatic Cocoa. You would normally just return the NSString * directly. Outparams are rare in Cocoa. (Usually just NSErrors get this treatment from framework calls. Otherwise they conventionally use name like getString to differentiate them from normal get accessors which don't use the word "get".)
I hope -stringWithString was just an example. That method is almost never used in practice, since it's equivalent (in this case) to just using a #"string literal" (although that would muddy your example).
Instead of using a double pointer, would it not be more elegant to use an NSMutableString instead?
- (void)createAString:(NSMutableString *)str
{
[str setString:#"Hi all!"];
}
....
NSMutableString *createStr = [[NSMutableString alloc] init];
[self createAString: createStr];
NSLog(#"%#", createStr);
[createStr release];
Or, even better, just have the createAString method return an NSString.
- (NSString *)createAString
{
return #"Hi all!"; // this is autoreleased automatically
}
I wouldn't want to presume that your needs are this simple, though. =)
Ok, if you take a look at my two previous posts (Link #2 in particular), I would like to ask an additional question pertaining to the same code. In a method declaration, I am wanting to define one of the parameters as a pointer to an array of pointers, which point to feat_data. I'm sort of at a loss of where to go and what to do except to put (NSMutableArray*)featDataArray in the declaration like below and access each object via another pointer of feat_data type. BTW, sorry to be asking so many questions. I can't find some of the things like this in the book I am using or maybe I'm looking in the wrong place?
-(void)someName:(NSMutableArray*)featDataArray;
feat_data *featDataPtr = [[feat_data alloc] init];
featDataPtr = [featDataArray objectAtIndex:0];
Link #1
Link #2
Your declaration looks fine. "NSMutableArray*" is an appropriate type for your parameter. (Objective-C doesn't have generics so you can't declare anything about what's inside the array.)
One problem I see in your code is that you allocate an object for no reason and then throw away the pointer (thus leaking memory).
I don't know what it is that you are trying to do, so here are some things that you can do with an NSMutableArray:
- (void)someName:(NSMutableArray *)featDataArray {
feat_data *featDataPtr = [[feat_data alloc] init];
[featDataArray addObject:featDataPtr]; // add an object to the end
[featDataPtr release];
feat_data *featDataPtr2 = [[feat_data alloc] init];
[featDataArray replaceObjectAtIndex:0 withObject:featDataPtr2]; // replace an existing entry
[featDataPtr2 release];
feat_data *featDataPtr3 = [featDataArray objectAtIndex:0]; // get the element at a certain index
// do stuff with featDataPtr3
}