I was following the example on Beginning iPhone 3 Development. On Chapter 8 I made a mistake in code.
- (NSMutableDictionary *)mutableDeepCopy
{
NSMutableDictionary * ret = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
NSArray * keys = [self allKeys];
for (id key in keys) {
id oneValue = [self valueForKey:key];
id oneCopy = nil;
if ([oneValue respondsToSelector:#selector(mutableDeepCopy)])
oneCopy = [oneValue mutableDeepCopy];
else if ([oneValue respondsToSelector:#selector(mutableCopy)])
oneCopy = [oneValue mutableCopy];
if (oneCopy == nil)
oneCopy = [oneValue copy];
[ret setValue:oneCopy forKey: key];
}
return ret;
}
In the second responseToSelector, instead of the mutableCopy above, I have mistakenly written it as mutableDeepCopy. As a result my creation of mutable array from the regular one has failed to a simple copy.
As a result console will print error message like this:
2010-02-04 19:58:28.381 Sections[1806:20b] * WebKit discarded an uncaught exception in the webView:shouldInsertText:replacingDOMRange:givenAction: delegate: * -[NSCFArray removeObjectAtIndex:]: mutating method sent to immutable object
Now my question is, this is really hard for me to debug if I'm writing my own code instead of just copying it from book. How do I know at which line this "mutating method sent to immutable object" occurs?
Step 1. Use the debugger.
Run -> Debugger or Shift-Command-Y. When your program encounters an error like the one above, you can see where in the code it was stopped. You can see Apple's instructions on using the debugger for details, but basic stuff is pretty easy to figure out. The most important part is the thread list panel in the upper-left quadrant of the debugger. It'll allow you to move up and down through the stack to see where in your code the error occurred. Usually you'll be able to use this to determine which one of your objects was declared as immutable and not mutable.
Step 2. Use Instruments.
Instruments is powerful and will allow you to do some pretty nifty stuff. In this situation, once you find out the memory address if your accidentally-immutable object, you can use Instruments to see the history of that object and hopefully trace it back to it's origin. To use instruments to track an object, you'll want to run Instruments with Object Allocation (Run -> Run with Performance Tool -> Object Allocations). If you know the address of the fouled-up object, you can search for it in the lower-right corner of Instruments, in the search box. Open the Extended Detail view (Command-E) to see where that object has been.
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 have a block based enumeration setup to go through an array of NSDictionaries like this:
__block NSURL *contentURL;
//This method of enumerating over the array gives the bad_access error
[documents enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *aName = [(NSDictionary *)obj objectForKey:#"Name"];
if([aName isEqualToString:name]) {
contentURL = [NSURL URLWithString:[(NSDictionary *)obj objectForKey:#"Content"]];
*stop=YES;
}
}];
NSLog(#"Content URL for issue with name %# is %#", name, contentURL);
Which if I use this method I get a EXC_BAD_ACCESS error on contentURL when I try to print it out in the NSLog statement.
If however, I enumerate through the array like this:
NSURL *contentURL;
//This method of enumerating over the array works fine
for (NSDictionary *obj in documents) {
NSString *aName = [obj objectForKey:#"Name"];
if([aName isEqualToString:name]) {
contentURL = [NSURL URLWithString:[obj objectForKey:#"Content"]];
}
}
NSLog(#"Content URL for issue with name %# is %#", name, contentURL);
All works fine. Why is this?
I ran into a similar problem a while ago.
Turns out that some of the block-based enumeration methods wrap the enumeration in an autorelease pool. Since you're assigning an autoreleased object, it gets deallocated before -enumerateObjectsUsingBlock: returns.
(I ran into the problem with -[NSDictionary enumerateKeysAndObjectsUsingBlock:, but the same principle applies here)
Try this instead:
contentURL = [[NSURL alloc] initWithString:[(NSDictionary *)obj objectForKey:#"Content"];
Out of interest, are you using ARC? If you are, I'd have expected it to add in a -retain on assignment.
Edit:
You are using ARC, so this isn't the answer to your question. Assigning to a __block variable will retain the object (unless you've encountered a bug in ARC, which is unlikely. The code you've provided doesn't have this issue when compiled using Apple LLVM 5.0).
It is likely that your problem is elsewhere and changing from using the convenience constructor is just masking the problem.
Likewise, the autorelease pool set up for the duration of the enumeration is likely revealing a problem that is caused elsewhere in your code. It explains why switching to using fast enumeration seems to fix the problem, but as before it is likely just masking a problem caused elsewhere in your code.
I'll leave the answer here because the information about autorelease pools may still be relevant to people who stumble across this.
If you're using ARC, as you say you are, then the code you show cannot produce the problem you describe. You must have some problem somewhere else, or your code is not as you describe.
When using the XCode analyzer I get a message saying:
Potential leak of an object allocated
The code this is in my NSData(String) category, the code is:
- (NSString*) utf8String
{
return [[NSString alloc] initWithData:self encoding:NSUTF8StringEncoding];
}
Now how can I solve this? When I change the statement to:
- (NSString*) utf8String
{
return [[[NSString alloc] initWithData:self encoding:NSUTF8StringEncoding] autorelease];
}
My application crashes on the line where I call utf8String.
The cocoa naming conventions suggest that all methods return autoreleased objects, with the exception of methods whose names start with 'init', 'copy' or 'new'. The static analyzer knows and checks this.
You have two choices. You can rename the method to -newUTF8String, or you can return an autorelease object and retain it when you want to store the return value of this method.
I would prefer the latter, but both would be valid code.
I guess your application crashes because the variable is released before it is used. It is recommended to call retain if you do not use the return value right away but store it in a member variable.
...
myMemberVariable = [something utf8String];
[myMemberVariable retain];
...
To make sure that you do not produce a memory leak you have to release the member variable somewhere. A good place for that would be dealloc.
- (void)dealloc {
if (myMemberVariable) [myMemberVariable release];
[super dealloc];
}
I would also recommend having a look at Advanced Memory Management Programming Guide to get some detailed information about memory management of iOS.
i've a function like this:
#property(nonatomic,retain) NSMutableArray *array;
#synthesize array = _array;
(NSMutableArray *) name
{
self.array = [[NSMutableArray alloc]init];
[_array addObject:object];
[object release];
return [_array autorelase];
}
In the other function i've a property like the property above, named result, and i make:
self.result = [... name];
Then in dealloc i make
[_result release];
and it crashes in this point, how can i solve this?
I've tried many roads, but or it crashes, or i see memory leak in Instruments, where am i wronging?
Thanks.
While there's a lot wrong with this code, the likely cause of your crash is that you're releasing object within -name without taking ownership of it- unless you're creating object within the method through a call to -alloc, -new, or -copy, that method doesn't own it and isn't responsible for releasing it. This is causing that object to be invalid within the NSMutableArray, so when _result releases, it attempts to release an invalid piece of memory and crashes.
Also, properties aren't simply local variables for individual functions, they're member variables for instances of the class for which you're writing these classes. If your end goal is only to return an autoreleased array and set it to result you could do the following:
- (NSMutableArray *) name {
//call a convenience method- it comes back autoreleased
NSMutableArray* theArray = [NSMutableArray array];
[theArray addObject:object];
//don't release object unless you took ownership of it in this function
return theArray;
}
then outside the function, either call self.result = [... name] or [self setResult:[... name]];
You have a very strange method definition (the header should have a - before the return type), and inside that definition you are accessing a variable called object that doesn't seem to exist. I'm not sure what you want, but you've got at least one memory problem. The array that you create in name gets leaked every time the method is called. If you add some details, like the crash message, someone may be able to help more.
I am facing some strange memory leak in our existing iPad application,
Here is a function which gives memory leak in instrument
-(NSString *)retriveInfo:(NSString*)fromstring:(NSString*)searchstring
{
NSArray *arrRetrive = [fromstring componentsSeparatedByString:searchstring];
if([arrRetrive count]!=0){
if ([arrRetrive count]!=1){
NSString *strDisplayOrder = [arrRetrive objectAtIndex:1];
arrRetrive = [strDisplayOrder componentsSeparatedByString:#"<"]; //MEMORY LEAK
}
}
return [arrRetrive objectAtIndex:0];
}
Here is a input parameter
Param 1 : <displayorder>1</displayorder><filename>201103153_0100.pdf</filename><title>【1面】東日本巨大地震直撃、日経平均一時675円安[br]原発関連売られる、東電はS安</title><category>トップ・注目株</category><dirpath>/var/www/mssite/webapp/data/pdf/20110315</dirpath><TimeStamp>201103141700</TimeStamp><FirstPageImg>20110315top.png</FirstPageImg></pagedata>
Param 2: <displayorder>
Basically i want to found (parse) value between start and end tag.
(I know NSXMLParser class but asper i explain this one is existing code and if i changed in code its too much time consuming)
Any suggetion?
With Regards
Pankaj Gadhiya
The code you've posted doesn't look like it's leaking memory -- all the methods you're calling are of the autorelease type (i.e. there's no new, alloc, copy, or retain in the code).
It's probably the code you have that calls retrieveInfo and does something with the result that's leaking memory (e.g. overretaining it). The leaks tool is pointing you at the componentsSeparatedByString because that's where the memory was allocated which was eventually involved in a memory leak.
Can you show us how you call retrieveInfo and what you do with the result?
Btw, what's the point of this nested if?
if([arrRetrive count]!=0){
if ([arrRetrive count]!=1)
It's wasteful, you might as well write this and get the same effect:
if ([arrRetrive count] > 1)
That leak probably means that you're over-retaining the return value. And leaks-tool just shows you the place where it was created.
You are leaking the NSArray when you re-assign a new NSArray inside the if block. The pointer to the original array is lost, which means that the runloop cannot release the memory allocated for the first result. The memory allocated for the second result is released.
You should really use a proper parser... however to address the leak, the following will work.
-(NSString *)retriveInfo:(NSString*)fromstring:(NSString*)searchstring
{
NSArray *arrRetrive = [fromstring componentsSeparatedByString:searchstring];
NSString *result = nil;
if([arrRetrive count]!=0){
if ([arrRetrive count]!=1){
NSString *strDisplayOrder = [arrRetrive objectAtIndex:1];
result = (NSString *)[[strDisplayOrder componentsSeparatedByString:#"<"] objectAtIndex:0];
}
}
return result;
}
What do you do with the object that's returned by this method? [arrRetrive objectAtIndex:0] is likely what's being leaked.
Leaks indicates that line with componentsSeparatedByString: because that's where the strings in the array are allocated. But the array isn't leaked.
Go into Leaks, look at a leaked instance, and click that little arrow button. You'll see all the retains and releases of the leaked object, which should point you to the problem.