iPhone calculator app crashing when trying to add object to NSSet - iphone

I'm getting through the Fall 2010 version of the Stanford class CS193P, iPhone programming. On assignment 2 I'm improving upon a calculator app created in assignment one. It seems that I'm almost finished but the app crashes when I try to press a variable located on the interface (for these purposes, "x").
Using my limited debugging skills, I managed to track the problem down. The problem comes in the method "(NSSet)variablesInExpression:(id)anExpression".
+ (NSSet *)variablesInExpression:(id)anExpression
{
NSMutableSet *setOfVariables = [[NSSet alloc] init];
for (NSString *str in anExpression) {
if ([str hasPrefix:VARIABLE_PREFIX]) {
[setOfVariables addObject:str];
}
}
[setOfVariables autorelease];
return setOfVariables;
}
When I get to the line
[setOfVariables addObject:str];
the app crashes. I've been trying to figure it out for a couple hours, please help! Is there a way in XCode to see the entire list of values in 'anExpression'?

Although you declare your variable as mutable set you create instance of immutable NSSet class - you must create NSMutableSet instance:
NSMutableSet *setOfVariables = [[NSMutableSet alloc] init];

Related

Objective-C Memory Management: crash on release

I'm new to Objective-C and can't seem to get the memory management code right. I have the following code:
Media* myMedia = [self.myMediaManager getNextMedia];
self.navigationItem.title = [self.myMediaManager getCategory];
[self.btnImage setImage:myMedia.imageFile forState: UIControlStateNormal];
[self.lblImage setText:myMedia.imageLabel];
//[myMedia release];
My app crashes if I uncomment the above line. Do I need to do something special when I instantiate myMedia?
EDIT:
If myMediaManager is supposed to release it, when would it do that. Here is my code for getNextMedia:
- (Media*) getNextMedia {
DLog(#"Start");
Media* nextMedia = [[Media alloc] init];
[self setNextMediaIndex];
if (self.mediaIndex > -1)
{
nextMedia = [mediaArray objectAtIndex: self.mediaIndex];
}
return nextMedia;
}
EDIT2: I fixed the crashing issue (I was releasing an object I didn't own). I still see leaks and can't seem to find what the issue is.
Only objects that you own can be released.
You can release objects if you new, alloc, copy, mutableCopy or retain them first.
Since there is no alloc/copy/retain in [self.myMediaManager getNextMedia]; you can't release it.
Since myMedia is not retained here, you don't need to release it. When the origin (self.myMediaManager) releases it, it gets destroyed immediately.
NSString *string = [[NSString alloc] init];
[string release]; // now we have to release the string, since we allocated it.
NSString *string = self.navigationItem.title;
// now we don't, since it's a property of `navigationItem` and we didn't retain it.
At this point, since you are just learning, you should probably just start off using ARC with the iOS5 beta versions of XCode. It's good to have an understanding but using ARC will save you many potential pitfalls - by the time you learn enough to produce something iOS5 will be out. You can still build applications targeting iOS4, so you'll still be able to reach a lot of people.
The general rule for memory management is as follows:
For every retain, alloc, copy, or new, you need to call release or autorelease.
Since you did not call any these, you do not need to release myMedia.
For more information, take a look at this other answer I posted that deals with the subject. Also, since you are new to iOS development, I suggest looking at this answer as well.
This updated code is suspicious:
Media* nextMedia = [[Media alloc] init];
[self setNextMediaIndex];
if (self.mediaIndex > -1)
{
nextMedia = [mediaArray objectAtIndex: self.mediaIndex];
}
Depending on the condition in the if() clause, you assign a new value to nextMedia, which makes the value you just allocated unreachable, i.e. it can't be released.
Also, you don't retain the value you get from the array, so you should not release it either. But if the if() clause does not run, you still have the instance you alloc-ed, and that should be released.
That is not good. Try:
Media* nextMedia = [[Media alloc] init];
[self setNextMediaIndex];
if (self.mediaIndex > -1)
{
[nextMedia release];
nextMedia = [[mediaArray objectAtIndex: self.mediaIndex] retain];
}
You could also do (and I would prefer that):
Media *nextMedia;
[self setNextMediaIndex];
if (self.mediaIndex > -1)
{
nextMedia = [[mediaArray objectAtIndex: self.mediaIndex] retain];
}
else
{
nextMedia = [[Media alloc] init];
}
Now you can release nextMedia when that is not needed anymore, without any ambiguity about the retain count.

Accessing NSDictionary problem

I'm only new to iPhone development, so my apologies if this is a silly question. I'm building various apps based on a book I'm reading and one of their suggestion was to build a mini web browser. I thought this would be easy, but while most of it is, I'm seriously struggling with the NSDictionary.
I have a UISegmentedControl used to display various bookmarks. The bookmark name that is displayed on the buttons of the UISegmentedControl is going to be my key and the url is the value associated with it.
I first try to declare an NSDictonary as a private (global variable), but since I could not get it to work, I resorted to declare it in my header file as follows:
#property (nonatomic, retain) NSDictionary *bookmarks;
I synthesize it and I initialized it in the viewDidLoad as follows:
- (void)viewDidLoad
{
bookmarks = [NSDictionary
dictionaryWithObjectsAndKeys:#"http://www.microsoft.com",
#"Microsoft",
#"http://www.google.com",
#"Google",
#"http://www.apple.com",
#"Apple",
#"http://msdn.microsoft.com",
#"MSDN", nil];
[super viewDidLoad];
}
I then associated a control with my segmented control and when the event is triggered and the function is called I've got the following code which is called:
- (IBAction) getShortcut:(id)sender
{
NSString *shortcut;
shortcut = [shortcuts titleForSegmentAtIndex:shortcuts.selectedSegmentIndex];
NSString *url = [bookmarks valueForKey:shortcut];
//[self navigateTo: url];
[url release];
}
When a button from the UISegmentedControl is clicked, I extract the value and stored it into shortcut and then I try to use the shortcut variable as a key to extract the associated value from the NSDictionary "bookmarks" but it keeps crashing on NSString *url = [bookmarks valueForKey:shortcut];
and bombs out of my function and displays the usual error EXC_BAD_ACCESS
Any help would be greatly appreciated.
T.
You have two options. one is to deal with the ivar directly as #Matt S. posted. Note that in this case you need to keep you object with enough retain count. You're using and auto released object and causing the error.
The other option is to use the property you already defined:
self.bookmarks = [[NSDictionary ...]];
And the property retains it.
That dictionary is autoreleased.
Try this:
self.bookmarks = [NSDictionary dictionaryWithObjectsAndKeys...]
You didn't retain the NSDictionary.
Do:
bookmarks = [[NSDictionary
dictionaryWithObjectsAndKeys:#"http://www.microsoft.com",
#"Microsoft", nil] retain];
The problem is, that you do not retain "bookmarks" in the viewDidLoad method. There is an naming convention mentioned somewhere in the Apple docs: If an intialisation method starts with "init..." the returned object is retained, if not you have to do it yourself. The "dictionaryWithObjectsAndKeys" returns and object with retain count 0, which means, that after the scope of assignment (your viewDidLoad method) it is immediatly released again.
So just put a
[bookmarks retain];
after your initalisation and you are done. Another solution which does the retaining for you
bookmarks = [[NSDictionary alloc] initWithObjectsAndKeys ...];
And you shouldn't release the url in your action. It gets released, once you release the dictionary

NSMutableArray addObject freezes my app

Tons of similar questions, but none of their answers seem to answer mine...
I'm creating my array as such:
imgArray = [NSMutableArray arrayWithCapacity:10];
Later (in another function) I am trying to add an object to it:
Attachment newAttachment = [[[Attachment alloc] init] autorelease];
newAttachment.fileName = filename;
newAttachment.file = file;
[imgArray addObject:newAttachment];
This results in the iPhone app freezing up. The simulator seems fine; the clock on the status bar keeps ticking, I don't get any error messages, but my app is no longer responding.
What am I doing wrong?
It seems you are not retaining imgArray. Are you? Try,
imgArray = [[NSMutableArray alloc] initWithCapacity:10]
if not.
just do
imgArray = [[NSMutableArray arrayWithCapacity:10] retain];
all class methods if return an object is returned with retain count 1 and object already in autorelease pool so if want to use that object beyond the current working block the you should always retain that object because then reference is lost outside the block.

iPad large NSArray - initWithObjects vs. ArrayWithObjects

can anyone clear this up for me ?
I am building an iPad App that has a TableViewController that is supposed to show something between 1000 and 2000 strings.
I have those NSStrings in a Singleton.
In the init Method of the Singleton I initialize an Array that holds all the data ( does not have to be the final way to do it - was just a quick copy and paste for testing )
I did an self.someArray = [[NSArray alloc]initWithObjects: followed by the large number of strings, followed by nil.
that worked fine in the simulator - but crashed with bad access on the iPad right on Application startup
If I use the convenience method [NSArray arrayWithObjects:instead - it works fine.
I looked into Instruments and the overall memory footprint of the App is just about 2,5 MB.
Now I don't know why it works the one way but not the other.
EDIT:
#import "StaticValueContainer.h"`
static StaticValueContainer* instance = nil;
#implementation StaticValueContainer
#synthesize customerRatingKeys;
+(StaticValueContainer*)sharedInstance
{
if (instance == nil){
instance = [[StaticValueContainer alloc]init];
}
return instance;
}
-(id)init
{
if ( ( self = [super init] ))
{
[self initCustomerRatingKeys];
}
return self;
}
-(void)init customerRatingKeys
{
self.customerRatingKeys = [[NSArray alloc]initWithObjects:
#"string1",
....
#"string1245"
,nil
}
as I said: it crashes on the device with self.customerRatingKeys = [[NSArray alloc]initWithObjects:
but works with *self.customerRatingKeys = [[NSArray arrayWithObjects...`
Well, there isn't much difference between them: arrayWithObjects returns an auto-released array that you don't need to release yourself (unless you subsequently retain it), and initWithObjects returns an array you must then release to avoid a memory leak. Performance wise there is no difference between them.
I would suggest if you're getting a bad access error using initWithObjects but not with arrayWithObjects there might be some sort of memory management error in your code. If you post the code itself you'll probably get a more exact response.

Class variable type gets changed

So in my view controller, I run code to populate an NSArray of Customer (custom class) objects. This custom class has objects that are of ANOTHER custom class called Address (a customer has a billing address and a shipping address). In the view controller when a customer in the list is selected, it passes a new view controller a customer object, like so:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
InfoViewController *customerinfoViewController = [[InfoViewController alloc] initWithStyle:UITableViewStyleGrouped andCustomer:[[[customers objectAtIndex:indexPath.section] objectAtIndex:indexPath.row] retain]];
[self.navigationController pushViewController:customerinfoViewController animated:YES];
[customerinfoViewController release];
}
The first time I visit this view controller while running the application, it works fine. However, when I revisit the view controller, something interesting happens. The application crashes, with unrecognized selector sent to instance 0x00whatever. Using the mouseover debugging feature in xCode, I am finding that the first object of the customer's shipAddress variable has its type changed from NSString to NSIndexPath. This does not happen to the customer's billAddress object. Anyone have any idea what is going on here? It seems like I may be having memory management issues but I would definitely like a confirmation on this before I tear my code apart tracking down all the retains and releases....
EDIT: More information here. with the following code, I have an NSMutableArray at the class level. At each iteration of the loop, I am looping through nodes in XML (which works fine). Every time a new letter is detected as the first letter of the name, I create a new subarray and add the customer to it, thus filling my class-level NSMutableArray (customers) with subArrays of customers for each letter of the alphabet detected. My question is about the retains and releases of the cycling customer object. Clang Static says there is an over-retaining error on the customer, but when I fix it according to Clang, the loop crashes. what gives? Related code below:
DDXMLDocument *rootDoc = [[[DDXMLDocument alloc] initWithData:xmlData options:0 error:nil] autorelease];
NSArray *elems = [rootDoc nodesForXPath:#"QBXML/QBXMLMsgsRs/CustomerQueryRs/CustomerRet" error:nil];
DDXMLNode *node;
sectionTitles = [[[NSMutableArray alloc] initWithCapacity:1] retain]; // Letters for UITableView section titles
NSMutableArray *subArray;
NSString *lastchar = #"A";
NSString *testchar;
int indexCount = -1;
customers = [[[NSMutableArray alloc] initWithCapacity:[elems count]] retain];
Customer *newCust;
for (int i = 0; i < [elems count]; i++) {
node = [elems objectAtIndex:i];
newCust = [[Customer alloc] initWithCustomerRetNode:node];
testchar = [[newCust fullName] substringToIndex:1];
if (i == 0 || ![[testchar uppercaseString] isEqualToString:lastchar]) {
[sectionTitles addObject:testchar];
lastchar = testchar;
indexCount++;
subArray = [[NSMutableArray alloc] initWithCapacity:1];
[customers addObject:subArray];
[subArray release];
[[customers lastObject] addObject:[newCust retain]];
}
else {
[[customers lastObject] addObject:[newCust retain]];
}
[newCust release];
}
NOTE: this code works for the most part, but clang doesn't like it.
EDIT: Addresses in the Customer class are assigned like so (which now does not work after Clang fixes)
...
else if ([tempname isEqualToString:#"BillAddress"])
billAddress = [billAddress initWithAddressNode:tempnode];
else if ([tempname isEqualToString:#"ShipAddress"])
shipAddress = [shipAddress initWithAddressNode:tempnode];
...
It sounds like you are having a over release issue, so yes memory management, you might be overreleasing that array you are storing your objects in.Cant really tell from the snippet of code though. Youll have to go and look through the code and find the source. Also using Clang Static Analyzer might be of help to you.