iPad app crashing with no crash log while reading from file - objective-c

The basic structure of my program has the user select an item from a UITableView, which corresponds to a stored text file. The file is then read into an array and a dictionary, where the array has the keys (I know I can just get the keys from the dictionary itself, this isn't my question).
The view is then changed to a UISplitView where the master view has the keys, and the detail view has the items in the dictionary attributed to that key. In this case, it's a series of "Yes/No" questions that the user selects the answer to.
My problem is this: When I click on a cell in the UITableView (first screen), it works fine, the data is read in perfectly, and so on. When I go back to the UITableView and click on the same cell again, the program crashes. Here is the read-in-from-file method:
-(NSArray *)readFromFile:(NSString *)filePath{
// NSLog(#"Path was: %#", filePath);
NSString *file = [[NSString alloc] initWithContentsOfFile:filePath];
// NSLog(#"File was: %#", file);
NSScanner *fileScanner = [[NSScanner alloc] initWithString:file];
NSString *held;
NSString *key;
NSMutableArray *detailStrings;
NSMutableArray *keys = [[NSMutableArray alloc] init];
NSMutableDictionary *details = [[NSMutableDictionary alloc] init];
/**
This is where the fun stuff happens!
**/
while(![fileScanner isAtEnd]){
//Scan the string into held
[fileScanner scanUpToString:#"\r" intoString:&held];
NSLog(#"Inside the while loop");
// If it is a character, it's one of the Key points, so we do the management necessary
if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:[[held lowercaseString] characterAtIndex: 0]]){
NSArray *checkers = [[NSArray alloc] initWithArray:[held componentsSeparatedByString:#"\t"]];
NSLog(#"Word at index 2: %#", [checkers objectAtIndex:2]);
if(detailStrings != nil){
[details setObject:detailStrings forKey:key];
[detailStrings release];
}
NSLog(#"After if statement");
key = [checkers objectAtIndex:2];
[keys addObject:(NSString *) key];
detailStrings = [[NSMutableArray alloc] init];
}
else if ([[NSCharacterSet decimalDigitCharacterSet] characterIsMember:[[held lowercaseString] characterAtIndex: 0]]){
NSArray *checkers = [[NSArray alloc] initWithArray:[held componentsSeparatedByString:#"\t"]];
NSLog(#"Word at index 1: %#", [checkers objectAtIndex:1]);
[detailStrings addObject:[checkers objectAtIndex:1]];
}
}
NSLog(#"File has been read in");
[details setObject:detailStrings forKey:key];
NSArray *contents = [[NSArray alloc] initWithObjects:(NSMutableArray *) keys, (NSMutableDictionary *) details, nil];
[detailStrings release];
return contents;
}
I've determined that the program crashes inside the
if(detailStrings != nil)
statement. I figure this is because I'm missing some memory management that I am supposed to be doing, but don't have the knowledge of where it's going wrong. Any ideas as to the problem, or why it is crashing without giving me a log?

detailStrings is not initialized when you enter the while loop. When you declare NSMutableArray *detailStrings; inside a method, detailStrings is not automatically set to nil. So when you do
if ( detailStrings != nil ) { .. }
it enters the if statement and since it is not initialized, it will crash when you access detailStrings.
Another thing is that detailStrings won't be initialized if it enters the else part of the loop first. That will cause a crash too. So based on your requirement, either do
NSMutableArray *detailStrings = nil;
or initialize it before you enter the while loop.

Deepak said truth. You should initialize detailStrings with nil first.
But there is second possible issue:
I recommend also to set nil after release, because in the next loop you may test nonexistent part of memory with nil.
if(detailStrings != nil){
[details setObject:detailStrings forKey:key];
[detailStrings release];
detailStrings = nil;
}
And the third possible issue: depending from incoming data you may go to the second part of IF statement first time and try to addObject into non-initialized array.
The fourth (hope last): you have memory leak with "checkers" arrays

Here's what I'm seeing:
//read in the file
NSString *file = [[NSString alloc] initWithContentsOfFile:filePath];
//create the scanner
NSScanner *fileScanner = [[NSScanner alloc] initWithString:file];
//declare some uninitialized stuff
NSString *held;
NSString *key;
NSMutableArray *detailStrings;
//initialize some stuff
NSMutableArray *keys = [[NSMutableArray alloc] init];
NSMutableDictionary *details = [[NSMutableDictionary alloc] init];
//begin loop
while(![fileScanner isAtEnd]){
//scan up to a newline
[fileScanner scanUpToString:#"\r" intoString:&held];
//see if you scanned a lowercase string
if ([[NSCharacterSet lowercaseLetterCharacterSet] characterIsMember:[[held lowercaseString] characterAtIndex: 0]]){
//make an array
NSArray *checkers = [[NSArray alloc] initWithArray:[held componentsSeparatedByString:#"\t"]];
//do a check... against an uninitialized value
if(detailStrings != nil){
//set a potentially uninitialized value into an array with an uninitialized key
[details setObject:detailStrings forKey:key];
At this point, you're pretty much hosed.
The fix:
properly initialize your variables
run the static analyzer
read the memory management programming guide

Related

iOS/Objective-C: Attempting to Scan a string for substrings which will be assigned to multiple NSStrings

I'm attempting to complete the Stanford iPhone Programming (FA10) assignement "Flickr Fetcher" -- so far things are going well, however I have come to an impasse:
I have successfully extracted the location of the "Top 100" pictures, which are formated in a string as "Country, State, City". I would like to create two NSStrings -- one being the country, the other string being the State and City. From where I can then do
cell.textLabel.text = countryString;
cell.detailTextLabel.text = stateCityString;
in my table view datasource methods.
From research on stackoverflow and the Apple Documentaion, NSScanner seems to be my best bet -- here is what I have so far...
- (void)viewDidLoad {
//Get the top 100 photos from Flickr
self.topPlacesArray = [FlickrFetcher topPlaces];
NSString *mainLabelString = [[NSString alloc] init];
NSString *stringFromArray = [[NSString alloc] init];
//This retrieves the string of the location of each photo
stringFromArray = [topPlacesArray valueForKey:#"_content"];
NSScanner *theScanner = [NSScanner scannerWithString:stringFromArray];
NSCharacterSet *commaSet = [[NSCharacterSet alloc] init];
commaSet = [NSCharacterSet characterSetWithCharactersInString:#","];
while ([theScanner isAtEnd] == NO) {
if ([theScanner scanUpToCharactersFromSet:commaSet intoString:&stringFromArray]) {
NSLog(#"%#",stringFromArray);
}
}
I'm just trying to see if the string properly substrings itself -- however I am getting a "SIGBART" at the beggining of the while loop, the error is this:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI length]: unrecognized selector sent to instance 0x8939eb0'
From all the documentation I have seen on NSScanner, it seems I have it set up properly, however, no matter what changes I do, it seems unable to even begin the loop.
What do I have to do to set up NSScanner properly, to avoid the "SIGABRT"? (for the record, i'm assuming "SIGABRT" is a segfault?). Thank you all for your time, you all are the best!
(Btw: I know this is not fully implemented yet for both country and state-city, i just want to get used to NSScanner, I will implement the rest once I get NSScanner under control)
EDIT 1: SosBorn! You are incredible! Thank you so much! So I have implemented this for my viewDidLoad:
- (void)viewDidLoad
{
self.topPlacesArray = [FlickrFetcher topPlaces];
NSArray *ArrayOfStrings = [[NSArray alloc] init];
NSArray *placeElements = [[NSArray alloc] init];
NSString *country = [[NSString alloc] init];
NSString *city = [[NSString alloc] init];
NSString *state = [[NSString alloc] init];
ArrayOfStrings = [topPlacesArray valueForKey:#"_content"];
for (NSString *place in ArrayOfStrings) {
placeElements = [place componentsSeparatedByString:#", "];
if ([placeElements count] == 3 && [placeElements objectAtIndex:0] != nil) {
city = [placeElements objectAtIndex:0];
[self.cityArray addObject:city];
state = [placeElements objectAtIndex:1];
[self.stateArray addObject:state];
country = [placeElements objectAtIndex:2];
[self.countryArray addObject:country];
NSLog(#"%#, %#, %#", city, state, country);
}
else {
NSLog(#"Did this work?");
}
}
[ArrayOfStrings release];
[placeElements release];
[country release];
[city release];
[state release];
[super viewDidLoad];
}
This worked like a complete charm BUT i'm having some bad access going on in the Delegate when trying to access self.window.rootViewController = self.viewController -- this doesn't make any-sense (i actually have a completely empty table, etc...) -- so i'm thinking I played with bad memory management with my substring-ing and now it gets in trouble with this delegate call.
Chuck, I was very interested in your comment as I was taught that the proper way to make variables is to call [myclass alloc] init]; and then release when you are done -- as I have. Of course my objective-C greenness is showing a bit... blush.
You all and this incredible community are such an asset to us Students -- thank you for all your time and dedication. The only path to progress is a path of cooperation!
EDIT 2: Ok -- now it's totally fixed with no terrible leaking problems. Chuck you were right! I had the pricniples of alloc init completely mixed up in my head -- here was my final solution:
NSMutableArray *array1 = [[NSMutableArray alloc] init];
NSMutableArray *array2 = [[NSMutableArray alloc] init];
NSMutableArray *array3 = [[NSMutableArray alloc] init];
self.cityArray = array1;
self.countryArray = array2;
self.stateArray = array3;
[array1 release];
[array2 release];
[array3 release];
NSArray *ArrayOfStrings = [topPlacesArray valueForKey:#"_content"];
NSArray *topPlaces = [NSArray arrayWithArray:ArrayOfStrings];
NSArray *topPlacesSorted = [topPlaces sortedArrayUsingSelector:#selector(compare:)];
ArrayOfStrings = topPlacesSorted;
for (NSString *place in ArrayOfStrings) {
NSArray *placeElements = [place componentsSeparatedByString:#", "];
if ([placeElements count] == 3 && [placeElements objectAtIndex:0] != nil) {
NSString *city = [placeElements objectAtIndex:0];
[self.cityArray addObject:city];
NSString *state = [placeElements objectAtIndex:1];
[self.stateArray addObject:state];
NSString *country = [placeElements objectAtIndex:2];
NSString *stateAndCountry = [NSString stringWithFormat:#"%#, %#", state, country];
[self.countryArray addObject:stateAndCountry];
NSLog(#"%#, %#, %#", city, state, country);
}
else {
NSLog(#"Nil Request");
}
Thank you again SosBorn, i was feeling like I had forgotten the basics of CS ಠ_ಠ.
The only thing that really bothers me is why do we have to initialize instance NSMutableArrays that way -- i found this was the only way to get them to actually work.
Not totally sure why it is crashing, but I think another approach to this would serve you better. You have a topPlacesArray, why not iterate through the array and process each array entry seperately? I am making some assumptions about the topPlacesArray, but it would look something like this:
for (NSString *place in topPlacesArray)
{
//Place is probably in this format: "Country, State, City"
NSArray *placeElements = [place componentsSeperatedByString:#","];
//This should give you an array with three elements. Country State and city.
NSString *country = [placeElements objectAtIndex:0];
NSString *cityState = [NSString stringWithFormat:#"%#, %#", country, cityState];
//Now you have your strings that you need. Do whatever you need to do with them.
//Add them to an array or set the value of a text label, etc.
}
Didn't take the time to handle memory management but you get the idea.

Objective C - NSString - Memory basics

I am trying to return an NSString that has been initialized from a plist.
If I comment out my release lines this code works. I would however like to release these objects from memory as I no longer need them.
I thought that 'initWithString' would copy the contents of the target string into my new string meaning I could safely release the NSMutableArray. But it isn't. Why not?
+ (NSString*) genImage {
NSString *path = [[NSBundle mainBundle] pathForResource:
#"Images" ofType:#"plist"];
NSMutableArray *arrayOfImages = [[NSMutableArray alloc] initWithContentsOfFile:path];
NSLog(#"%d", [arrayOfImages count]);
int indexToLoad = 0;
NSString *res = [[NSString alloc] initWithString:[arrayOfImages objectAtIndex:indexToLoad] ];
[arrayOfImages release];
[path release];
return res;
}
You do not retain the return value of -[NSBundle pathForResource:ofType:] (the path variable), so there is no need to release it (and doing so will cause a crash, most likely). However, you should autorelease res, as you do retain that. You can change your last line to
return [res autorelease];

Initialization of an NSDictionary in Objective C (for iOS)

I am relatively new to Objective-C and now I have a problem in my iPhone app that I don't fully understand.
I try to use a NSMutableDictionary, this does not seem to work as i expect for some reason. When I run the debugger and do po numberToCallerMap to see the dictionary, I get an exception. I have read the documentation for NSMutableDictionary on how to initialize it, but I can not see what I am doing wrong. Help and advice are appreciated. The variable causing me problem is numberToCallerMap, here is the relevant function:
- (void)setData:(NSString*)value{
[list release];
list = [[NSMutableArray alloc] init];
SBJSON *json = [[[SBJSON alloc] init] autorelease];
NSMutableDictionary* numberToCallerMap;
CallerInfo* caller;
NSDictionary* callerInfo;
#try {
NSArray *array = (NSArray*)[json objectWithString:value];
// reading all the items in the array one by one
numberToCallerMap = [NSMutableDictionary dictionary];
for (id *item in array) {
// if the item is NSDictionary (in this case ... different json file will probably have a different class)
NSDictionary *dict2 = (NSDictionary *) item;
CallInfo *data = [CallInfo alloc];
[data initFromDictionary:dict2];
callerInfo = (NSDictionary*)[dict2 valueForKey:#"caller"] ;
//Here, we want the phonenumber to be part of the CallerInfo object instead.
// It is sent from the server as part of the Call-object
NSString* number = (NSString*)[dict2 valueForKey:#"phoneNumber"];
[callerInfo setValue:number forKey:#"phoneNumber"];
caller = (CallerInfo*)[numberToCallerMap valueForKey:number];
if(caller == nil || [caller isKindOfClass:[NSNull class]]){
caller = [CallerInfo alloc];
[caller initFromDictionary:callerInfo];
[numberToCallerMap setValue:caller forKey:number];
[list insertObject:caller atIndex:0];
}
[caller addRecentCall:data];
}
}
#catch (NSException * e) {
[list release];
list = [[NSMutableArray alloc] init];
}
#finally {
[numberToCallerMap release];
}
}
This is probably not the only problem, but you are not alloc-ing your numberToCallerMap dictionary, you are getting it from a convenience class method -- [NSMutableDictionary dictionary] -- that returns it autoreleased. So you should not call release on it yourself.

Objective C - UITableView after calling reloadData my object properties are null/nil

I have a ViewController defined as follows:
#interface SectionController : UITableViewController {
NSMutableArray *sections;
}
- (void) LoadSections;
When LoadSection is call it makes a call to NSURLConnection to load a url which in turn calls
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[connection release];
[responseData release];
NSDictionary *results = [responseString JSONValue];
NSMutableArray *jSections = [results objectForKey:#"Items"];
sections = [NSMutableArray array];
for (NSArray* jSection in jSections)
{
Section* section = [Section alloc];
section.Id = [jSection objectForKey:#"Id"];
section.Description = [jSection objectForKey:#"Description"];
section.Image = [jSection objectForKey:#"Image"];
section.Parent = [jSection objectForKey:#"Parent"];
section.ProductCount = [jSection objectForKey:#"ProductCount"];
[sections addObject:section];
[section release];
}
[jSections release];
[results release];
[delegate sectionsLoaded];
[self.view reloadData];
}
The data parses correctly and I now have sections filled with many items.
Calling [self.view reloadData] forces a callback to the delegate method cellForRowAtIndexPath which should then present the data into the cell however its at this point that sections is now nil again.
Can someone please point out my mistake? I must admit I am a newbie to objective c and it probably a pointer issue. What is need to do is retain the value of sections after calling reloadData.
Many thanks.
Seeing the new code the problem is obvious:
sections = [NSMutableArray array];
should become
[sections release];
sections = [[NSMutableArray alloc] init];
note that the array does not become again "nil", is instead deallocated and you get an invalid reference, which might (should) generate a crash on dereferencing.
I suggest you to read some articles on reference counted memory management as it might be not obvious if you are new to Objective-C, and often leads to mistake (i.e: autorelease is not magic at all)
best way to avoid all memory leaks here is just simply use #property (nonatomic, retain) NSMutableArray *sections; by using property you can be sure that all men management works will be correctly managed by system. Just don't forget that property retains value when you doing setSections:, so that you need to pass autoreleased object here.
self.sections = [NSMutableArray array];
...
[self.sections addObject:section];
Also to avoid all problem try to make all objects which should live only in this method autorelease. Like this:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *responseString = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] autorelease];
NSDictionary *results = [responseString JSONValue];
NSMutableArray *jSections = [results objectForKey:#"Items"];
self.sections = [NSMutableArray array];
for (NSArray* jSection in jSections) {
Section* section = [[[Section alloc] init] autorelease];
section.Id = [jSection objectForKey:#"Id"];
section.Description = [jSection objectForKey:#"Description"];
section.Image = [jSection objectForKey:#"Image"];
section.Parent = [jSection objectForKey:#"Parent"];
section.ProductCount = [jSection objectForKey:#"ProductCount"];
[self.sections addObject:section];
}
[delegate sectionsLoaded];
[self.view reloadData];
}
And also most of object you trying to release already autoreleased:
all params passed into your method shouldn't be released manually, check I think JSONValue also should returns autoreleased object and anything you getting by enumerating or by call objectForKey:

Problem in memory manegment?

I developing an application, in which i working with database manipulation. The method i have written in database class as follows.
-(NSMutableArray *)getData: (NSString *)dbPath{
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
if(sqlite3_open([dbPath UTF8String], &database) == SQLITE_OK){
NSString *sqlQuery = [NSString stringWithFormat:#"SELECT empID, addText FROM Employee WHERE nameID = %#", nameID];
sqlite3_stmt *selectstmt;
if(sqlite3_prepare_v2(database, [sqlQuery UTF8String], -1, &selectstmt, NULL) == SQLITE_OK){
while (sqlite3_step(selectstmt) == SQLITE_ROW){
[dataArray addObject:[[NSMutableDictionary alloc] init]];
[[dataArray lastObject] setObject:[NSString
stringWithFormat:#"%d", sqlite3_column_int(selectstmt, 0)] forKey:#"empID"];
[[dataArray lastObject] setObject:[NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt,1)] forKey:#"addText"];
}
}
sqlite3_finalize(selectstmt);
}
sqlite3_close(database);
return dataArray;
}
The above code work fine on the simulator but cannot on device.
I also was tracing the memory leaks , in which i founding that memory leak in above method code. But i not able to solve the that memory leak.
Now i also find out memory leak in following method.
(id)initWithString:(NSString *)str attributes:(NSDictionary *)attributes
{
if ((self = [super init]))
{
_buffer = [str mutableCopy];
_attributes = [NSMutableArray arrayWithObjects:[ZAttributeRun attributeRunWithIndex:0 attributes:attributes], nil];
}
return self;
}
The leak near _buffer = [str mutableCopy];. And leak trace gives me in the output continuous increasing NSCFString string allocation. How i maintain it?
Thanks in advance.
Your leak is that you don't release either the dataArray object, nor the mutable dictionaries you create in the while loop. Consider autoreleasing the mutable array, and manually releasing the dictionaries after you add them to the array.
As for why it "doesn't work" on the device, you need to be more specific about what happens and why that isn't what you expect.
Your inner loop leaks the NSMutableDictionary objects, as you should release them after adding to the array, i.e.
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict setObject:[NSString stringWithFormat:#"%d", sqlite3_column_int(selectstmt, 0)] forKey:#"empID"];
[dict setObject:[NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt,1)] forKey:#"addText"];
[dataArray addObject:dict];
[dict release];
Also, your whole method should most probably return an autoreleased object by naming convention. Not sure if this is a leak - depends on how you call that method and if you release the returned value.
So maybe use
return [dataArray autorelease];
From the first glance you have 2 places where leaks can be:
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
...
return dataArray;
Caller method is responsible for releasing array returned from your method - check if it does.
Also your method name is not consistent with obj-c guidelines - they suggest that methods returning non-autoreleased object( so caller is responsible for releasing them) should contain create, alloc, copy in their name. So it could be better to return autoreleased array (return [dataArray autorelease]; from this method and let caller decide whether it needs to retain array or not.
Second place is
[dataArray addObject:[[NSMutableDictionary alloc] init]];
It is leaking dictionary object, you should probably just write
[dataArray addObject:[NSMutableDictionary dictionary]];
Your method contains two call to +alloc that don't have corresponding calls to -release or -autorelease.
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
...
[dataArray addObject:[[NSMutableDictionary alloc] init]];
You can rewrite these lines like this to get rid of the leak:
NSMutableArray *dataArray = [NSMutableArray array];
...
[dataArray addObject:[NSMutableDictionary dictionary]];