I'm a total noob to iPhone programming, and I've run into an exception being thrown that I just can't wrap my head around.
Background: The error is happening in a custom subview, and occurs immediately upon loading the program. I'm getting an exception thrown in the overridden drawRect method. The code throwing the error follows:
- (void)drawRect:(CGRect)rect{
NSNumber *points = [NSNumber numberWithInt: star.numberOfPoints];
//HERE. Doesn't recognize selector?!
CGPathRef t = (CGPathRef)[starPaths objectForKey:points];
/*snip*/
starPaths is initialized in awakeFromNib as an NSMutableDictionary with capacity 1.
The exception that's getting thrown is -[NSObject doesNotRecognizeSelector:]
starPaths is declared in the header file for the view as
NSMutableDictionary *starPaths;
and is initialized as
- (void)awakeFromNib{
starPaths = [NSMutableDictionary dictionaryWithCapacity: 1];
}
Finally, I haven't been able to get to a point in the code where I successfully add elements to the dictionary, since the code to add an entry relies on receiving a nil response from the dictionary to know that the that specific entry needs to be built.
Any suggestions? Any other information I should provide?
Any help at all would be appreciated, I feel like I'm missing something obvious, but I've been bashing my head against this all day with no luck.
If you do not retain the starPaths variable or explicitly allocate it yourself with [[NSMutableDictionary alloc] initWithCapacity:1] then it will be automatically released on the next iteration of the run loop.
You need to do
starPaths = [[NSMutableDictionary dictionaryWithCapacity:1] retain];
Or
starPaths = [[NSMutableDictionary alloc] initWithCapacity:1];
Just make sure to release it when you no longer need it.
A few things to check:
Is starPaths declared as an NSMutableDictionary* instance variable? You mention that it is initialized. Did you use the dictionaryWithCapacity method (which returns an auto-release object) or initWithCapacity (which needs to be explicitly retained). To be safe, you may want to retain it and release it when done.
Double-check to make sure the header files are properly included so the declaration of starPaths is included in implementation files that make use of it.
Generally speaking if you're getting mystery errors it has to do with corrupted memory. Try putting a breakpoint on the first line of the drawRect method and do a "po starPaths" in the debugger console window to see what's in it and what type of object the runtime thinks it is.
Related
I noticed that my program was crashing because it was running out of memory. I figured out that this was happening because of this code segment:
DataSet *tempSet = [[DataSet alloc] init];
tempSet.rightFoot = [NSNumber numberWithDouble:temp1];
tempSet.leftFoot = [NSNumber numberWithDouble:temp2];
[footData addObject:tempSet]; //add dataSet object to the array
[tempSet release];
I read some tutorials about memory management online and was able to figure out that I needed to do this: (notice the added "autoreleases")
DataSet *tempSet = [[DataSet alloc] init];
tempSet.rightFoot = [[NSNumber numberWithDouble:temp1] autorelease];
tempSet.leftFoot = [[NSNumber numberWithDouble:temp2] autorelease];
[footData addObject:tempSet]; //add dataSet object to the array
[tempSet release];
I am still confused about why I had to do this. I did not use alloc, new or copy when creating the numberWithDouble.
Does this mean that I would need to add autorelease in this situation as well?:
[subset addObject:[NSNumber numberWithDouble:temp]];
What about this situation?:
tempSet.rightFoot = [NSString stringWithString:#"temp"];
I appreciate any help.
+numberWithDouble
is called a convenience method. Meaning, it replaces the little section of code that would look like this:
[[[NSNumber alloc]initWithDouble:double]autorelease];
Most (if not all) convenience methods are auto release by default, so the OP code with the autoreleases is incorrect, as it drops the retain count to -1.
The equals sign however is equivalent to
[self setRightFoot:[[[NSString alloc]initWithString]autorelease]];
which increments rightFoot's retain count and requires it to be released elsewhere.
as for the -addObject code, it returns void, so it does not in fact increment the receiver's retain count, and requires no release of the receiver. The object in the array should already be released by the convenience method for later, which doesn't matter because the array is now holding "copy" of it.
This is not an answer (I just do not know how to comment -- I only see "share, edit, flag"), but just a few info about Memory Management in iOS:
1. Don't release objects that you do not own.. ---> owned objects are usually the ones you "alloc", "new", "copy." //And probably the one in your #property wherein you "retain" an object.
2. when you "autorelease" an object, don't "release" it afterwards, because that would mean you're releasing the same object twice.
But there's ARC already, so you better upgrade your Xcode to avoid overreleasing objects / memory leaks (for not releasing objects)..
If there's something wrong or inappropriate with the one I put here, please edit. :)
I have just been through a client project initially coded by us then abused by them! And we are getting a strange crash, which is consisten on each mac individually but inconsistent between the team or after a reload/restart of Xcode/OSX.
The problem is one I have come across before when I havent been cleaning up objects properly, but I have been through static analyser and am still struggling.
The crashes always seem to occur because objects we have created in the VC are getting overwritten by others therefore changing their type so valid method calls are being thrown as exceptions.
For example:
NSNumber *test1;
-(void)viewDidLoad {
test1 = [NSNumber numberWithInteger:7];
}
-(void)someOtherMethod {
NSLog(#"what was test? %#", [test stringValue]);
}
This is currently throwing an unrecognized selector exception during the NSLog as hovering over test1 tells me is is of type NSURLRequestInternal, if I restart Xcode it will probably be somewhere else!!
How can I debug/solve this!?!?
That should crash every time.
Let's look at the code.
test1 = [NSNumber numberWithInteger:7];
This creates a NSNumber and assigns it to the instance. However, the NSNumber is scheduled for deallocation as soon as the main event loop is run.
NSLog(#"what was test? %#", [test stringValue]);
This, presumably, is being run after the main event loop has had a swing. At this point, test (and I assume this should be test1) is pointing to a dangling pointer. Accessing it will crash.
There is no substitute to reading Apple's documentation on memory management: http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html
This will explain what you need to know. But basically, you should be using [[NSNumber alloc] initWithInteger:7] or retain it (possibly using property syntax) instead.
But unless you read Apple's documentation on the subject, you're going to be chasing memory bugs forever.
you forgot to retain test1 so it gets autoreleased, hence the crash
try using NSZombieEnabled for debugging this kind of stuff.
test1 = [NSNumber numberWithInteger:7];
should either be
test1 = [[NSNumber numberWithInteger:7] retain];
or (better)
test1 = [[NSNumber alloc] initWithInteger:7];
numberWithInteger: returns an autoreleased object, so by the time someOtherMethod is called, the object that test1 points to has been deallocated.
Hey. I have been working on a Twitter application and have been stuck on a EXC_ BAD_ ACCESS error for quite some time. I know that EXC_ BAD_ ACCESS is a memory issue but i cannot pinpoint where the problem is. Here is my code sample:
- (void)viewDidLoad {
[super viewDidLoad];
NSString *path = #"/Volumes/Schools/BHS/Student/740827/Documents/Forrest McIntyre CS193P/Presence2";
NSArray *propList = [NSArray arrayWithContentsOfFile:[NSBundle pathForResource:#"TwitterUsers" ofType:#"plist" inDirectory:path]];
people = [[NSMutableArray alloc]init];
for (NSString *name in propList) {
Person *p = [[Person alloc] initWithUserName: name];
[people addObject: p];
[p release];
}
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
The exception is thrown on the last brace after the comment. I believe that it is truly thrown in the for loop somewhere but just shows up upon exiting.
Here is the implementation file for Person:
#implementation Person
#synthesize image;
#synthesize username;
#synthesize displayName;
#synthesize statusArray;
-(id)initWithUserName:(NSString *)userName {
if(self = [super init])
{
self.username = userName;
NSDictionary *info = [TwitterHelper fetchInfoForUsername:userName];
self.displayName = [info objectForKey:#"name"];
NSLog([NSString stringWithFormat:#"%#",[info objectForKey:#"profile_image_url"]]);
NSString *imageURL2 = [NSString stringWithFormat:#"%#",[info objectForKey:#"profile_image_url"]];
self.image = [UIImage imageWithData: [NSData dataWithContentsOfURL: [NSURL URLWithString: imageURL2]]];
[info release];
self.statusArray = [TwitterHelper fetchTimelineForUsername:userName];
}
return self;
}
#end
Thanks for any help
EDIT: Here is the header file for PersonListViewController (the class that contains the ViewDidLoad).
This is just to show you where people is coming from.
#interface PersonListViewController : UITableViewController {
NSMutableArray *people;
}
#end
since you never retain propList or path you shouldn't be releasing them.
You should, however, release people
For an overview of memory management, see the Memory Management Programming Guide
For quick fixes, try the static analyzer.
I think the problem is here:
[propList release];
Since you created propList using arrayWithContentsOfFile you don't need to release it - it will be automatically released. The autorelease is actually what's causing the error since it is trying to release something that you already released manually.
ETA: as cobbal mentioned, you also don't need to release path.
Debugging EXC_BAD_ACCESS is difficult to debug. This happens when a message is sent to an object that is already released. You need to find out what is causing this generic error by turning on NSZombiEnabled environment variable so the Objective-C environment will be able to 'track' a deallocated object. Using this, when you get the error you can determine where the error occurred by looking at the call stack. You won't know where it is released but at least it will get you close.
I don't have it setup here, but you may also be passing a pointer to the error which will cause the object to not persist as a zombie/dummy.
Bottom line, you need to make sure the variables you are meaning to release, that you retain them as necessary.
This Technical Q&A by Apple gives tips on Finding bugs with EXC_BAD_ACCESS.
For one, neither of these are necessary in your example:
[path release];
[propList release];
because:
path is a string literal (will always exist)
propList is autoreleased
For any EXC_BAD_ACCESS errors, you are usually trying to send a message to a released object. The BEST way to track these down is use NSZombieEnabled.
This works by never actually releasing an object, but by wrapping it up as a "zombie" and setting a flag inside it that says it normally would have been released. This way, if you try to access it again, it still know what it was before you made the error, and with this little bit of information, you can usually backtrack to see what the issue was.
It especially helps in background threads when the Debugger sometimes craps out on any useful information.
VERY IMPORTANT TO NOTE however, is that you need to 100% make sure this is only in your debug code and not your distribution code. Because nothing is ever released, your app will leak and leak and leak. To remind me to do this, I put this log in my appdelegate:
if(getenv("NSZombieEnabled") || getenv("NSAutoreleaseFreedObjectCheckEnabled"))
NSLog(#"NSZombieEnabled/NSAutoreleaseFreedObjectCheckEnabled enabled!");
If you need help finding the exact line, Do a Build-and-Debug (CMD-Y) instead of a Build-and-Run (CMD-R). When the app crashes, the debugger will show you exactly which line and in combination with NSZombieEnabled, you should be able to find out exactly why.
http://www.cocoadev.com/index.pl?NSZombieEnabled can be useful in tracking down EXC_BAD_ACCESS bugs. Instead of deallocating objects when they are released it puts them into a zombie state that raises an exception when they are subsequently accessed. Just be sure not to ever release code with this flag set, as it will leak memory like a sieve.
what is self.editButtonItem? I don't see it in your .h file
A couple of things.
In initWithUserName: you're getting info from a method that doesn't contain alloc/copy/create. Further, you don't explicitly retain it. Yet you release it. This is problematic assuming fetchInfoForUsername: autoreleases its result as expected according to the Cocoa Memory management rules.
Using property accessors in initializers is considered bad form since it can cause KVO notifications to be sent out for a half-baked instance.
I'm encountering an extremely vexing problem. I have a UITableViewController which in its init method decodes a dictionary from a JSON or plist file (I've tried both), then retrieves an array from that dictionary. Later on, in the method tableView:numberOfRowsInSection:, I'm returning the count of that array.
However, for reasons beyond me, calling count on the array at that point crashes the application, though calling count directly after assignment in init doesn't. Also, if I replace the initial assignment with a programmatically created array (via NSArray initWithObjects), it works fine.
JSON decoding in init:
NSString *jsonPath = [[NSBundle mainBundle] pathForResource:#"Categories" ofType:#"json"];
SBJSON *jsonParser = [SBJSON new];
NSDictionary* dict = [jsonParser objectWithString:[NSString stringWithContentsOfFile:jsonPath encoding:NSUTF8StringEncoding error:nil]];
categories = [dict objectForKey:#"ContentCategories"];
// Outputs correct count
NSLog(#"Count: %#", [NSNumber numberWithInt:[categories count]]);
Programmatic init:
categories = [[NSArray alloc] initWithObjects: [[NSDictionary alloc] initWithObjectsAndKeys:#"Junk", #"Title"]];
// Outputs correct count
NSLog(#"Count: %#", [NSNumber numberWithInt:[categories count]]);
UITableViewController number of rows method:
// Outputs correctly if programmatically created, crashes if decoded from JSON/plist
NSLog(#"Count: %#", [NSNumber numberWithInt:[categories count]]);
I've tried the "categories" variable as an ivar, a propertied-ivar, and as a class variable, with no luck.
Thanks for your help!
I'm not familiar with that JSON parser, but if it's following Cocoa conventions, objectWithString will be returning an autoreleased object. This means, it will be deallocated on the next iteration through the runloop, unless you retain it. When Cocoa collection classes deallocate, they release each object they contained, so categories will also get released. This is why it works at first, but not later in the program.
The reason it works by making it a property, is that the synthesized setter created retains categories (assuming you set your property with the retain parameter, which I assume you did), so it won't get released. That is the proper solution, nice work. :) Calling retain on categories would also have worked.
The Cocoa convention is that if a method name contains "alloc" or "copy", or begins with "new", the object returned has a retain count of 1, and you are responsible for releasing it. Otherwise, the returned object has a retain count of zero (it has been autoreleased), and will be deallocated on the next iteration through the runloop, unless you retain it (in which case you take responsibility for releasing it later).
I recommend reading this:
Memory Management Programming Guide for Cocoa
Edit:
The reason it worked using [[NSArray alloc] initWithObjects...] is, because that method contains "alloc", the returned array has not been autoreleased, and so is still around later when you access it.
Note: changing "categories" to a property and adding "self." in front of all of the setting/getting methods (e.g., self.categories = ...) made this work. For some reason, calling just "categories" was returning an object of type UIWindow in the numberOfRows method (!). That seems incredibly odd to me (I would hope I would just encounter compiler errors), but then again, I'm relatively new to obj-c.
I've been staring at this same issue for a long time now, and would really appreciate any help or suggestions. I'm sure its something simple, but I can't seem to find it. In my app delegate I'm loading up a bunch of accessory objects (an object I created, which supports NSCopying) with the following code:
NSString *path = [[NSBundle mainBundle] pathForResource:#"Accessories" ofType:#"plist"];
NSDictionary *accDict = [[NSDictionary alloc] initWithContentsOfFile:path];
self.colors = (NSArray *) [accDict objectForKey:#"Colors"];
self.exteriorAccessories = [self loadAccessoriesForMode:EXTERIOR_MODE withDictionary:accDict];
self.interiorAccessories = [self loadAccessoriesForMode:INTERIOR_MODE withDictionary:accDict];
[accDict release];
And this is the definition for the method its calling:
-(NSArray *)loadAccessoriesForMode:(NSString *)mode withDictionary:(NSDictionary *) dictionary
{
NSMutableArray *tempValues = [[NSMutableArray alloc] init];
for (NSDictionary *value in [dictionary objectForKey:mode])
{
Accessory *accessory = [[Accessory alloc] initWithDictionary:value];
[tempValues addObject:accessory];
[accessory release];
}
NSArray *returnArray = [[NSArray alloc] initWithArray:tempValues copyItems:YES];
[tempValues release];
[returnArray autorelease];
return returnArray;
}
When I get to the release for accDict I'm getting an EXC_BAD_ACCESS exception. If I take out the release of accessory inside the loop, everything is fine - but I'm leaking Accessory objects (which seems obv. to me - if I init it and I alloc it, its my job to release it).
When I step through this in the debugger, I'm seeing the init, copy and dealloc methods all fire on my Accessory object as expected. I can also post code for the Accessory object if you think it will help, but I think the problem is somewhere in this code.
I think I've found the cause, but I'll post it here so others can possibly benefit. It didn't really have anything to do with the code I posted. Rather the problem was inside of the Accessory object. I was setting things directly instead of calling the getters through self.
So this:
value = [dict objectForKey:#"myKey"];
Instead of this:
self.value = [dict objectForKey:#"myKey"];
Somehow this was causing me to have bad side effects on the NSDictionary itself (I thought that was not mutable, but it seems I was somehow messing things up). The only way I found this was to use the very helpful advice that I found on Cocoa With Love.
When I used the Print Description option in XCode, I was able to see that the NSDictionary somehow contained AccessoryValue objects - one of my custom objects that should NOT have been there since this was just loaded from a simple plist. Print Description can be found in XCode by hovering over the object to see its details (while the process is paused in the debugger) and clicking on the little up/down arrows right next to the triangle that expands into object details. For dictionaries, this will dump their entire contents to the console.
Please prefix this with an "I know nothing about objective C but":
It looks to me like you need to release the accessory items after you have copied them into the "returnArray", or maybe not specify "copyItems".
Run Clang on your code. It's a godsend. Clang rules! It will do Static Analysis of your code and tell you what you might be leaking. Great stuff.
I was battling with the Exc_Bad_Access issue for a day and finally found the answer. The issue was when I try to access one of the objects stored in an NSDictionary, the first time was OK but the second access the object turned to be nil even though the object counts in the dictionary remains the same. This strange behavior was due to releasing the object twice. Here is an example:
NSString* nstring=[[[NSString alloc]init]autorelease]
[AnNSDictonaryInstance setObject:nstring forKey:0];
...
[nstring release];
Notice that nstring was set autorelease then release again? It won't show problem right away unit you try to read the dictionary object the second times. I hope one day Apple's development team will be able to flag this as an violation while compiling.
I hope this post will help someone out.
Wayne of Campbell