I have a few views which parse xml downloaded from the internet.
The instruments leaks tool tells me I have leaks when I release data members inside the dealloc method but not when i put the [objectname release]; inside viewDidDissapear.
Is this a cardinal sin?
Coming from a c/c++ background I find obj-c memory management very confusing!
EDIT: Here is the code:
#import "SuggestedFriendList.h"
#implementation SuggestedFriendList
#synthesize maincell,nationalityimageview, subjectimageview, accommodationimageview;
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict
{
currentElement = [[elementName copy] autorelease];
if ([elementName isEqualToString:#"shared"])
{
tshared = [[NSMutableString alloc] init];
}
if ([elementName isEqualToString:#"fullname"])
{
tfullname = [[NSMutableString alloc] init];
}
if ([elementName isEqualToString:#"nationality"])
{
tnationality = [[NSMutableString alloc] init];
}
if ([elementName isEqualToString:#"subject"])
{
tsubject = [[NSMutableString alloc] init];
}
if ([elementName isEqualToString:#"accommodation"])
{
taccommodation = [[NSMutableString alloc] init];
}
if ([elementName isEqualToString:#"memberid"])
{
tmemberid = [[NSMutableString alloc] init];
}
if ([elementName isEqualToString:#"count"])
{
tcount = [[NSMutableString alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if ([currentElement isEqualToString:#"shared"])
{
[tshared appendString:string];
}
if ([currentElement isEqualToString:#"fullname"])
{
[tfullname appendString:string];
}
if ([currentElement isEqualToString:#"nationality"])
{
[tnationality appendString:string];
}
if ([currentElement isEqualToString:#"subject"])
{
[tsubject appendString:string];
}
if ([currentElement isEqualToString:#"accommodation"])
{
[taccommodation appendString:string];
}
if ([currentElement isEqualToString:#"memberid"])
{
[tmemberid appendString:string];
}
if ([currentElement isEqualToString:#"count"])
{
[tcount appendString:string];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:#"shared"])
{
[lshared addObject:tshared];
[tshared release];
}
if ([elementName isEqualToString:#"fullname"])
{
[lfullname addObject:tfullname];
[tfullname release];
}
if ([elementName isEqualToString:#"nationality"])
{
[tnationality appendString:#".png"];
[lnationality addObject:tnationality];
[tnationality release];
}
if ([elementName isEqualToString:#"subject"])
{
[lsubject addObject:tsubject];
[tsubject release];
}
if ([elementName isEqualToString:#"accommodation"])
{
[laccommodation addObject:taccommodation];
[taccommodation release];
}
if ([elementName isEqualToString:#"memberid"])
{
[lmemberid addObject:tmemberid];
[tmemberid release];
}
if ([elementName isEqualToString:#"count"])
{
count = [[NSMutableString alloc] init];
count = tcount;
[tcount release];
}
}
-(void)fetchsuggestions
{
MyManager *sharedManager = [MyManager sharedManager];
suggestiondetailxml = [[NSMutableData alloc] init];
NSString *urlString = [NSString stringWithFormat:#"http://secreturl.com/a.php?username=%#&password=%#",sharedManager.user,sharedManager.passw];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
connection = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:YES];
}
-(void) connection:(NSURLConnection *)conn didReceiveData:(NSData *)data
{
[suggestiondetailxml appendData:data];
}
-(void) connectionDidFinishLoading:(NSURLConnection *)conn
{
NSString *xmlcheck = [[[NSString alloc] initWithData:suggestiondetailxml encoding:NSUTF8StringEncoding] autorelease];
NSLog(#"%#",xmlcheck);
lshared = [[NSMutableArray alloc] init];
lfullname = [[NSMutableArray alloc] init];
lnationality = [[NSMutableArray alloc] init];
lsubject = [[NSMutableArray alloc] init];
laccommodation = [[NSMutableArray alloc] init];
lmemberid = [[NSMutableArray alloc] init];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData: suggestiondetailxml];
[parser setDelegate:self];
[parser parse];
[parser release];
//[xmlcheck release]; //causes crash
[connection release];
connection = nil;
[suggestiondetailxml release];
[[self tableView] reloadData];
NSLog(#"%#",lmemberid);
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
[[self navigationItem] setTitle:#"My Culture"];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self fetchsuggestions];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[lshared release];
[lfullname release];
[lnationality release];
[lsubject release];
[laccommodation release];
[lmemberid release];
//[count release];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [lfullname count];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 93.0;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
if (cell==nil)
{
[[NSBundle mainBundle] loadNibNamed:#"SuggestedFriendCell" owner:self options:nil];
cell = maincell;
//self.detailcell = nil;
}
UILabel *name;
name = (UILabel *)[cell viewWithTag:4];
name.text=[lfullname objectAtIndex:indexPath.row];
if(([[lshared objectAtIndex:indexPath.row]isEqualToString:#"all"])||([[lshared objectAtIndex:indexPath.row]isEqualToString:#"nationalityandaccommodation"])||([[lshared objectAtIndex:indexPath.row]isEqualToString:#"nationalityandsubject"])||([[lshared objectAtIndex:indexPath.row]isEqualToString:#"nationality"]))
{
UIImageView *nationality;
nationality = (UIImageView *)[cell viewWithTag:1];
nationality.image=[UIImage imageNamed:[lnationality objectAtIndex:indexPath.row]];
}
if(([[lshared objectAtIndex:indexPath.row]isEqualToString:#"all"])||([[lshared objectAtIndex:indexPath.row]isEqualToString:#"nationalityandaccommodation"])||([[lshared objectAtIndex:indexPath.row]isEqualToString:#"subjectandaccommodation"])||([[lshared objectAtIndex:indexPath.row]isEqualToString:#"accommodation"]))
{
UIImageView *accommodation;
accommodation = (UIImageView *)[cell viewWithTag:2];
accommodation.image=[UIImage imageNamed:#"accommodation.jpeg"];
}
if(([[lshared objectAtIndex:indexPath.row]isEqualToString:#"all"])||([[lshared objectAtIndex:indexPath.row]isEqualToString:#"nationalityandsubject"])||([[lshared objectAtIndex:indexPath.row]isEqualToString:#"subjectandaccommodation"])||([[lshared objectAtIndex:indexPath.row]isEqualToString:#"subject"]))
{
UIImageView *subject;
subject = (UIImageView *)[cell viewWithTag:3];
subject.image=[UIImage imageNamed:#"degree.jpg"];
}
return cell;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
/*
<#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:#"<#Nib name#>" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
*/
MyManager *sharedManager = [MyManager sharedManager];
int row = indexPath.row;
sharedManager.interrogatedmemberid=[lmemberid objectAtIndex:row];
ViewFriendProfile *frienddetail_vc = [[[ViewFriendProfile alloc] init] autorelease];
[[self navigationController] pushViewController:frienddetail_vc animated:YES];
}
-(void)dealloc
{
[super dealloc];
}
#end
When I uncomment [tcount release] it causes crashes: why is this.
PS: I am SO sorry about the formatting: How can you copy and paste code so it appears in the code blocks?
Thanks
To understand what's going on you need to understand how viewWillAppear and viewWillDisappear work.
Both methods are invoked multiple times in a view controller's life cycle depending on whether you're pushing/popping a viewcontroller, or if you're displaying / dismissing a modal from a view controller.
For example:
Push viewController A: viewDidLoad and viewWillAppear called on viewController A
Push viewController B from viewController A: viewWillDisappear called on viewController A, viewDidLoad and viewWillAppear called on viewController B
Pop viewController B to go back to viewController A: viewWillDisappear called on viewController B, viewDidUnload called on viewController B, dealloc called on viewController B, viewWillAppear called on viewController A
Display a modal from viewController A: viewWillDisappear called on viewController A
Dismiss the modal: viewWillAppear called on viewController A
Pop viewController A: viewWillDisappear called on viewController A, viewDidUnload called on viewController A, dealloc called on viewController A
Sounds like you're allocating an object in viewWillAppear, which is called multiple times, but deallocating it in the dealloc method, which is called only once, hence the memory leak.
Releasing your object in viewWillDisappear is balancing out the allocs in viewWillAppear hence there is no leak.
All I can say at this point is, unless you have a very good reason to allocate something in viewWillAppear (i.e., you know what you're doing), don't do it.
There's not much more I can tell you... posting more code for what you're trying to do might help you get a more elaborate answer :)
Hope this helped though.
EDIT after the code was posted.
First of all, do you mean 'when I uncomment [count release]'? Could you post the console message for that crash? It's crashing because parserDidEndElement is where it's allocated, but it is getting released in viewWillDisappear. If these don't balance out, you'll get an EXC_BAD_ACCESS
Coming to your memory situation. I see now why releasing in viewWillDisappear fixes the leaks. You're calling fetchsuggestions on viewWillAppear which in turn creates and fires off the URL request. This will lead to connectionDidFinishLoading being called on a successful connection. Here is where you're allocating the various ivars. Since I mentioned earlier that viewWillAppear is called multiple times, this means that you're firing off the connection multiple times which leads to connectionDidFinishLoading being called multiple times.
That is why releasing in viewWillDisappear fixes the leaks because for every allocate on a viewWillAppear, you balance it by releasing in viewWillDisappear.
What you missed though is that your program will crash when there's no network available. This is because connectionDidFinishLoading will not get called when there's no network. Now, when you try releasing in viewWillDisappear, you will get an EXC_BAD_ACCESS as you're trying to release previously deallocated instances which were never allocated again in viewWillAppear.
I don't think I see a need to call fetchsuggestions multiple times on viewWillAppear, unless you're doing it intentionally.
This is the most glaring thing I see right now... the issues could be (and probably are) multiple, but how about we start off by restructuring the code keeping in mind that multiple unbalanced allocs and releases inside viewWillAppear/Disappear might not be a good idea...? :)
It's kind of strange to release anything in viewWillDisappear, but if you allocate it in viewWillAppear, that's the right thing to do. I think you should be allocating your objects in viewDidLoad, and releasing them in viewDidUnload. In some cases that would avoid unnecessarily recreating them.
You should also release retained ivars in dealloc. Just make sure that when you release them in other methods, you set them to nil, so if they get released again, it's just sending release to nil, which is a no-op.
One tool that I find helpful in finding potential memory leaks is Build > Analyse in Xcode.
It tells you which line did the allocation and where might it leak later down the code. It also tells you where you have made incorrect [object release] decrement count.
There is a possibilty that you are not completely releasing the view and dealloc is never being called.
Related
I'm building an RSS-Reader and put a refreshbutton on the right corner of the navigation bar. It works fine and I get no crashes. But if if I press the refresh button during scrolling the app crashes. And I have no idea where the problem is. I analyzed the project but it couldn't find anything...
So here's the error I get:
2012-01-22 16:36:48.205 GYSA[712:707] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 5 beyond bounds for empty array'
*** First throw call stack:
(0x37adb8bf 0x315c11e5 0x37a24b6b 0x7913 0x34ef39cb 0x34ef2aa9 0x34ef2233 0x34e96d4b 0x37a3a22b 0x33231381 0x33230f99 0x3323511b 0x33234e57 0x3325c6f1 0x3327f4c5 0x3327f379 0x37249f93 0x3747b891 0x37aa4f43 0x37aaf553 0x37aaf4f5 0x37aae343 0x37a314dd 0x37a313a5 0x375affcd 0x34ec1743 0x2ac9 0x2a54)
terminate called throwing an exception(gdb)
And here's my code:
#import "RssFunViewController.h"
#import "BlogRssParser.h"
#import "BlogRss.h"
#implementation RssFunViewController
#synthesize rssParser = _rssParser;
#synthesize tableView = _tableView;
#synthesize appDelegate = _appDelegate;
#synthesize toolbar = _toolbar;
-(void)toolbarInit{
UIBarButtonItem *refreshButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh
target:self action:#selector(reloadRss)];
refreshButton.enabled = YES;
self.navigationItem.rightBarButtonItem = refreshButton;
[refreshButton release];
UIImage *image = [UIImage imageNamed: #"navigationbar.png"];
UIImageView *imageview = [[UIImageView alloc] initWithImage: image];
UIBarButtonItem *button = [[UIBarButtonItem alloc] initWithCustomView: imageview];
self.navigationItem.leftBarButtonItem = button;
[imageview release];
[button release];
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
self.view.autoresizesSubviews = YES;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self toolbarInit];
_rssParser = [[BlogRssParser alloc]init];
self.rssParser.delegate = self;
[[self rssParser]startProcess];
}
-(void)reloadRss{
[self toggleToolBarButtons:NO];
[[self rssParser]startProcess];
}
-(void)toggleToolBarButtons:(BOOL)newState{
NSArray *toolbarItems = self.toolbar.items;
for (UIBarButtonItem *item in toolbarItems){
item.enabled = newState;
}
}
//Delegate method for blog parser will get fired when the process is completed
- (void)processCompleted{
//reload the table view
[self toggleToolBarButtons:YES];
[[self tableView]reloadData];
}
-(void)processHasErrors{
//Might be due to Internet
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Achtung!" message:#"Leider ist es im Moment nicht möglich eine Verbindung zum Internet herzustellen. Ohne Internetverbindung ist die App nur in beschränktem Umfang nutzbar!"
delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alert show];
[alert release];
[self toggleToolBarButtons:YES];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [[[self rssParser]rssItems]count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"rssItemCell"];
if(nil == cell){
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"rssItemCell"]autorelease];
}
cell.textLabel.text = [[[[self rssParser]rssItems]objectAtIndex:indexPath.row]title];
cell.detailTextLabel.text = [[[[self rssParser]rssItems]objectAtIndex:indexPath.row]description];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[[self appDelegate] setCurrentlySelectedBlogItem:[[[self rssParser]rssItems]objectAtIndex:indexPath.row]];
[self.appDelegate loadNewsDetails];
[_tableView deselectRowAtIndexPath:indexPath animated: YES];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (void)dealloc {
[_appDelegate release];
[_toolbar release];
[_tableView release];
[_rssParser release];
[super dealloc];
}
#end
I found the code line that causes the problem:
cell.textLabel.text = [[[[self rssParser]rssItems]objectAtIndex:indexPath.row]title];
cell.detailTextLabel.text = [[[[self rssParser]rssItems]objectAtIndex:indexPath.row]description];
If I delete these codelines I can't reproduce the error. But they're necessary for the RSS feed as you can imagine :).
Any solutions?
Here's the fetching code:
#import "BlogRssParser.h"
#import "BlogRss.h"
#implementation BlogRssParser
#synthesize currentItem = _currentItem;
#synthesize currentItemValue = _currentItemValue;
#synthesize rssItems = _rssItems;
#synthesize delegate = _delegate;
#synthesize retrieverQueue = _retrieverQueue;
- (id)init{
self = [super init];
if(self){
_rssItems = [[NSMutableArray alloc]init];
}
return self;
}
- (NSOperationQueue *)retrieverQueue {
if(nil == _retrieverQueue) {
_retrieverQueue = [[NSOperationQueue alloc] init];
_retrieverQueue.maxConcurrentOperationCount = 1;
}
return _retrieverQueue;
}
- (void)startProcess{
SEL method = #selector(fetchAndParseRss);
[[self rssItems] removeAllObjects];
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self
selector:method
object:nil];
[self.retrieverQueue addOperation:op];
[op release];
}
-(BOOL)fetchAndParseRss{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
//To suppress the leak in NSXMLParser
[[NSURLCache sharedURLCache] setMemoryCapacity:0];
[[NSURLCache sharedURLCache] setDiskCapacity:0];
BOOL success = NO;
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
[parser setDelegate:self];
[parser setShouldProcessNamespaces:YES];
[parser setShouldReportNamespacePrefixes:YES];
[parser setShouldResolveExternalEntities:NO];
success = [parser parse];
[parser release];
[pool drain];
return success;
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict{
if(nil != qualifiedName){
elementName = qualifiedName;
}
if ([elementName isEqualToString:#"item"]) {
self.currentItem = [[[BlogRss alloc]init]autorelease];
}else if ([elementName isEqualToString:#"media:thumbnail"]) {
self.currentItem.mediaUrl = [attributeDict valueForKey:#"url"];
} else if([elementName isEqualToString:#"title"] ||
[elementName isEqualToString:#"description"] ||
[elementName isEqualToString:#"link"] ||
[elementName isEqualToString:#"guid"] ||
[elementName isEqualToString:#"pubDate"]) {
self.currentItemValue = [NSMutableString string];
} else {
self.currentItemValue = nil;
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if(nil != qName){
elementName = qName;
}
if([elementName isEqualToString:#"title"]){
self.currentItem.title = self.currentItemValue;
}else if([elementName isEqualToString:#"description"]){
self.currentItem.description = self.currentItemValue;
}else if([elementName isEqualToString:#"link"]){
self.currentItem.linkUrl = self.currentItemValue;
}else if([elementName isEqualToString:#"guid"]){
self.currentItem.guidUrl = self.currentItemValue;
}else if([elementName isEqualToString:#"pubDate"]){
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"yyyy-MM-dd'T'HH:mm:ss'Z'"];
self.currentItem.pubDate = [formatter dateFromString:self.currentItemValue];
[formatter release];
}else if([elementName isEqualToString:#"item"]){
[[self rssItems] addObject:self.currentItem];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if(nil != self.currentItemValue){
[self.currentItemValue appendString:string];
}
}
- (void)parser:(NSXMLParser *)parser foundCDATA:(NSData *)CDATABlock{
//Not needed for now
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{
if(parseError.code != NSXMLParserDelegateAbortedParseError) {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[(id)[self delegate] performSelectorOnMainThread:#selector(processHasErrors)
withObject:nil
waitUntilDone:NO];
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser {
[(id)[self delegate] performSelectorOnMainThread:#selector(processCompleted)
withObject:nil
waitUntilDone:NO];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
-(void)dealloc{
self.currentItem = nil;
self.currentItemValue = nil;
self.delegate = nil;
[_rssItems release];
[super dealloc];
}
#end
What you should be doing is copying your fetched data array to an ivar. Then populating your tableview from that ivar, and then in processCompleted copying the new data to the ivar and call reloadData. This will keep the the tableview from being in the inconsistent state you're experiencing.
#property (retain, nonatomic) NSArray *sourceArray;
- (void)processCompleted{
self.sourceArray = [[[[self rssParser]rssItems] copy] autorelease];
[self toggleToolBarButtons:YES];
[[self tableView]reloadData];
}
And then when populating the tableview refer to the copied array. For example:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"rssItemCell"];
if(nil == cell){
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"rssItemCell"]autorelease];
}
cell.textLabel.text = [[self.sourceArray objectAtIndex:indexPath.row]title];
cell.detailTextLabel.text = [[self.sourceArray objectAtIndex:indexPath.row]description];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
And similarly in every other tableview delegate method where you reference [[self rssParser]rssItems].
It could be that while you are scrolling and refreshing at the same time, your datasource is being emptied, before it is being filled. So, while your tableview thinks that it has 5 rows, your datasource does not have 5 items because you are downloading them from the wherever your source is. And when it queries for the fifth item, there is nothing there and your application falls over.
Edit
I was right. Your refresh code calls startProcess which empties out the array that you use to populate the array, and then you add to it an item at a time, and you are doing this in a background queue so it's probably asynchronous.
The solution to this is to write your new items to an intermediate array in your background queue, and when the process has finished, replace your current rssItems with this array and reload your tableview.
Check while reloading tableview. When you scroll reload tableview is automatically get called. So check is the array has values in it before assigning it's content to cell. Check array count is greter than 0 and then write these lines.
In my case adding this line of code at the beginning of the method called for refresh worked:
tableView.scrollEnabled = NO;
Of course you need to set your tableView again at the end:
tableView.scrollEnabled = YES;
I have a question here: Confusing double free error message/memory leak in iPhone app which I think needs a new question to answer it.
The code I am interested in is in that question but I will re post it here
#import <UIKit/UIKit.h>
#import "MyManager.h"
#interface ListOfCarShares : UITableViewController <NSXMLParserDelegate>
{
NSURLConnection *connection;
NSMutableData *carsharexml;
NSMutableArray *ldestination;
NSMutableArray *ldeparts_from;
NSMutableArray *lcs_id;
NSMutableArray *ltime;
NSMutableString *currentElement;
NSMutableString *tdest;
NSMutableString *tfrom;
NSMutableString *ttime;
NSMutableString *tid;
}
-(void)fetchcarshares;
#property (nonatomic, assign) IBOutlet UITableViewCell *maincell;
#end
#import "ListOfCarShares.h"
#implementation ListOfCarShares
#synthesize maincell;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict
{
currentElement = [[elementName copy] autorelease];
if ([elementName isEqualToString:#"destination"])
{
//NSLog(#"found current conditions tag it reads %#",currentElement);
tdest = [[NSMutableString alloc] init];
}
if ([elementName isEqualToString:#"departs_from"])
{
tfrom = [[NSMutableString alloc] init];
}
if ([elementName isEqualToString:#"time"])
{
ttime = [[NSMutableString alloc] init];
}
if ([elementName isEqualToString:#"cs_id"])
{
tid = [[NSMutableString alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if ([currentElement isEqualToString:#"destination"])
{
[tdest appendString:string];
}
if ([currentElement isEqualToString:#"departs_from"])
{
[tfrom appendString:string];
}
if ([currentElement isEqualToString:#"time"])
{
[ttime appendString:string];
}
if ([currentElement isEqualToString:#"cs_id"])
{
[tid appendString:string];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([currentElement isEqualToString:#"destination"])
{
[ldestination addObject:tdest];
[tdest release];
}
if ([currentElement isEqualToString:#"departs_from"])
{
[ldeparts_from addObject:tfrom];
[tfrom release];
}
if ([currentElement isEqualToString:#"time"])
{
[ltime addObject:ttime];
[ttime release];
}
if ([currentElement isEqualToString:#"cs_id"])
{
[lcs_id addObject:tid];
[tid release];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
ldestination = [[NSMutableArray alloc] init];
ldeparts_from = [[NSMutableArray alloc] init];
ltime = [[NSMutableArray alloc] init];
lcs_id = [[NSMutableArray alloc] init];
carsharexml = [[NSMutableData alloc] init];
[self fetchcarshares];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[connection release];
[ldestination release];
[ldeparts_from release];
[ltime release];
[lcs_id release]; ///
[carsharexml release];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [ltime count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:nil];
if (cell == nil)
{
[[NSBundle mainBundle] loadNibNamed:#"carsharecell" owner:self options:nil];
}
// Configure the cell...
cell=maincell;
UILabel *from;
UILabel *dest;
UILabel *time;
from = (UILabel *)[cell viewWithTag:4];
dest = (UILabel *)[cell viewWithTag:5];
time = (UILabel *)[cell viewWithTag:6];
from.text=[ldeparts_from objectAtIndex:indexPath.row];
dest.text=[ldestination objectAtIndex:indexPath.row];
time.text=[ltime objectAtIndex:indexPath.row];
return cell;
}
/*
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
*/
/*
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/
/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the item to be re-orderable.
return YES;
}
*/
-(void)fetchcarshares
{
MyManager *sharedManager = [MyManager sharedManager];
NSString *urlString = [NSString stringWithFormat:#"http://url/get.php?username=%#&password=%#",sharedManager.user,sharedManager.passw];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
connection = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:YES];
}
-(void) connection:(NSURLConnection *)conn didReceiveData:(NSData *)data
{
[carsharexml appendData:data];
}
-(void) connectionDidFinishLoading:(NSURLConnection *)conn
{
NSString *xmlcheck = [[NSString alloc] initWithData:carsharexml encoding:NSUTF8StringEncoding];
NSLog(#"%#",xmlcheck);
[xmlcheck release];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData: carsharexml];
[parser setDelegate:self];
[parser parse];
[parser release];
[[self tableView] reloadData];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 102;
}
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
}
-(void)dealloc
{
[super dealloc];
}
#end
I only have one property defined in the .h file. The people who answered that question seem to think that the reason I am having double free error is due to the fact I don't have #property for my variables.
I have lots of code practically identical to this and I don't have a problem.
My questions are
When should I use a property?
Should I be using properties here and why?
Thanks
You technically only need to use properties for values that are intended to be accessible from other classes, but many find it easier to use (retained) properties for all pointer-type instance variables so that the retaining is a bit more automatic. (And then use self.propertyName = xxx; notation for setting and self.propertyName = nil; for releasing in dealloc.)
Yes, you can do the retains and releases "manually", but it's a hair tedious to do so, and you tend to muck things up when you make "quick edits". The one thing you have to watch out for, though, is assigning a retained (not simply autoretained) value (such as your alloc/init values) to a self.xxx property. This will result in double retain, if you don't mitigate it somehow.
Another thing to do, if you don't use properties, is to always nil a pointer value after you release it. This prevents you from accidentally using the released value and and it prevents you from doing a double release.
(Note that it's in no way "bad programming" to use "lazy" techniques like I described above, vs "perfectly" figuring out everything. About 98% of programming is debugging, and anything you can do to prevent bugs or make them easier to find is goodness.)
(I'll also note that your problem in the above code appears to be mainly that you do not nil thetdest et al pointers after releasing them. And your if tests should likely check to see if the pointer has been nilled before using it.)
Added: Note that the above applies to pre-ARC programs. With ARC the "rules" change substantially.
Properties do a lot of things. At the most superficial level, they let you access your member variables in dotted form. At best, they can be excellent memory management tools (and more).
Let's say you have a variable:
NSNumber * myNumber;
Later in the code, you access it as:
myNumber = [NSNumber numberWithInt: 5];
The problem is that you might lose reference to the previously stored value in myNumber. Possible Memory Leak!! At this point, you don't have a retain on myNumber and it may get dealloc'd before you're done using it.
How can properties help? Let's say you defined a property around it and used synthesize:
In the interface definition:
NSNumber * myNumber;
...
#property (retain, nonatomic) NSNumber * myNumber;
and
In the implementation file:
#synthesize myNumber;
This will create a getter and setter. Meaning... everytime you assign myNumber to something as in:
self.myNumber = newNumber;
the following setter method (created by synthesize directive) gets invoked:
- (NSNumber *) setMyNumber: (NSNumber *) newNumber {
[myNumber release];
myNumber = newNumber;
[myNumber retain];
return newNumber;
}
Here, myNumber gets a retain automatically. This is very tedious to do by hand everytime... as you can see, it's much easier to use properties.
This is still not a perfect solution, though! Why? What if you use the following statement in your implementation:
myNumber = newNumber;
Remember, properties' getter and setter get invoked only if you're using the dotted notation (self.myNumber). So here, using properties has done nothing for us, 'cause we forgot to use them!
This is very common and likely lapse and understandingly frustrating.
So, what's the best way? This is what I recommend (as do countless others):
In the interface class:
NSNumber * _myNumber;
...
#property (retain, nonatomic) NSNumber * myNumber;
In the implementation file:
#synthesize myNumber = _myNumber;
Now, you can access your-number as:
self.myNumber = whateverNewNumber;
But, if you did:
myNumber = whateverNewNumber;
You'll get an error... because myNumber variable just doesn't exist... forcing you to use self.myNumber everytime!
Also, if you do choose to go this route, don't forget the dealloc:
- (void) dealloc {
[_myNumber release];
_myNumber = nil;
}
or more succinct:
- (void) dealloc {
self.myNumber = nil;
}
I will eventually get hold of this memory allocation idea and iphone development but i am struggling. Below is my code of my NSXMLParser for a twitter viewer i have made. It works fine and doesn't leak until about 30secs after the code is loaded... Its frustrating me because as soon as i get this parser done and without leaks I will be ready to go :).
I've watched loads of video tutorials and just need a help looking through my own code so i can get an idea.
rssParser.m
//
// rssParser.m
// template
//
// Created by Jonathan Pink on 17/08/2011.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "rssParser.h"
#implementation rssParser
#synthesize entries;
-(id)loadXMLData:(NSData *)xml{
entries = [[NSMutableArray alloc] init];
xmlParser = [[NSXMLParser alloc] initWithData:xml];
[xmlParser setDelegate:self];
[xmlParser parse];
return self;
}
-(void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
[currentElement release];
currentElement = [elementName copy];
if([elementName isEqualToString:#"item"])
{
RssData = [[rssData alloc] init];
currentRssDescription = [[NSMutableString alloc] init];
currentRssLink = [[NSMutableString alloc] init];
currentRssPubDate = [[NSMutableString alloc] init];
currentRssTitle = [[NSMutableString alloc] init];
}
}
-(void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
if([elementName isEqualToString:#"item"])
{
RssData.rssLink = currentRssLink;
RssData.rssDescription = currentRssDescription;
RssData.rssPubDate = currentRssPubDate;
RssData.rssTitle = currentRssTitle;
NSLog(#"currenttitle = %#",RssData.rssTitle);
}
if([elementName isEqualToString:#"item"])
{
[entries addObject:RssData];
RssData = nil;
}
}
-(void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
if([currentElement isEqualToString:#"title"])
{
[currentRssTitle appendString:string];
}
else if([currentElement isEqualToString:#"link"])
{
[currentRssLink appendString:string];
}
else if([currentElement isEqualToString:#"description"])
{
[currentRssDescription appendString:string];
}
else if([currentElement isEqualToString:#"pubDate"])
{
[currentRssPubDate appendString:string];
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser {
NSLog(#"all done!");
NSLog(#"stories array has %d items", [entries count]);
}
-(void)dealloc{
[currentElement release];
[currentRssDescription release];
[currentRssLink release];
[currentRssPubDate release];
[currentRssTitle release];
[xmlParser release];
[RssData release];
[entries release];
[super dealloc];
}
#end
and the twitter view controller
//
// TwitterViewController.m
// template
//
// Created by Jonathan Pink on 16/08/2011.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "TwitterViewController.h"
#import "ASIHTTPRequest.h"
#implementation TwitterViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
[config release];
[RssParser release];
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
UIBarButtonItem *anotherButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:#selector(ProcessAndParse)];
self.navigationItem.rightBarButtonItem = anotherButton;
[anotherButton release];
numberOfTextRows = 4;
config = [[Configuration alloc] init];
[super viewDidLoad];
[self ProcessAndParse];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
-(void)ProcessAndParse{
NSURL *url = [config urlForFeed:#"Twitter"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
NSData *responseData = [request responseData];
RssParser = [[rssParser alloc] loadXMLData:responseData];
[self.tableView reloadData];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([RssParser entries] == 0) {
return 1;
}
else
{
return [[RssParser entries]count];
}
}
/*#define FIXED_HEIGHT_SECTION 18
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
//RSSData *currentEntry = [[rssParser rssEntries] objectAtIndex:indexPath.row];
CGSize theSize = [[currentEntry rssDescription] sizeWithFont:[UIFont systemFontOfSize:12.0f] constrainedToSize:CGSizeMake(265.0f, 9999.0f) lineBreakMode:UILineBreakModeWordWrap];
// This gets the size of the rectangle needed to draw a multi-line string
numberOfTextRows = round(theSize.height / 18);
// 18 is the size of the font used in the text label
// This will give us the number of lines in the multi-line string
if ((indexPath.section == FIXED_HEIGHT_SECTION) || (numberOfTextRows < 2)) {
return 44;
// 44 is the default row height; use it for empty or one-line cells (or in other table sections)
} else {
return theSize.height + 35;
// 16 seems to provide a decent space above/below; tweak to taste
}
}*/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
rssData *currententry =[[RssParser entries] objectAtIndex:indexPath.row];
if ([RssParser entries] == 0) {
cell.textLabel.text = #"No Records";
}
else
{
cell.textLabel.text = [currententry rssTitle];
}
return cell;
}
/*
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
*/
/*
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/
/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the item to be re-orderable.
return YES;
}
*/
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
/*
<#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:#"<#Nib name#>" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
*/
}
#end
Any help i will be eternally grateful! :)
Did you try to profile your app with Instruments?
If you use the analyzation for memory leaks properly it will directly tell you where in the code the memory leak is caused.
This line looks a bit suspect
[entries addObject:RssData];
RssData = nil;
You are nullifying the pointer but not releasing what it is pointing to. Your code is fairly hard to follow because you are not following some standard conventions.
Your init method is not safe and it should start with init...
Try:
-(id)initWithXMLData:(NSData *)xml
{
self = [super init];
if (self) {
entries = [[NSMutableArray alloc] init];
xmlParser = [[NSXMLParser alloc] initWithData:xml];
[xmlParser setDelegate:self];
[xmlParser parse];
}
return self;
}
For naming things
RssData // Class names should start with uppercase
rssData // variables and ivars should start with lowercase
Otherwise it's hard to tell when you are working with a class or an instance of a class.
You should also look at using properties which would remove much of the complication of manual memory management.
my question depends on my other question
NSMutableArray vs NSArray
i have created a navigationController and load a TableView inside with data from the other question. Now a get a detailview and get new data from xml, so i copy my methods and modifide them.
but it is the same stucture, i does not change a lot.
But now i get the same error.
i have in detailview.h
NSMutableArray *seminareArray;
and
#property (nonatomic, retain) NSMutableArray *seminareArray;
in detailview.m
#synthesize SeminareListeTabelle, selectedSeminar, seminareArray, receivedData;
i add this code
seminareArray = [[NSMutableArray alloc] init];
self.seminareArray = [NSMutableArray arrayWithCapacity:10];
before i add the data. and i get the error here
cell.textLabel.text = [seminareArray objectAtIndex:row];
EXC_BAD_ACCESS again some type problem
i add data to array like this
if([elementName isEqualToString:#"seminar"])
{
//NSLog(#"%#", [attributeDict objectForKey:#"name"]);
NSString *seminarName = [NSString stringWithFormat:#"%#", [attributeDict objectForKey:#"name"]];
[seminareArray addObject:seminarName];
[seminarName release];
}
the array is filled with data, but after tableView reload, i get this error.
//
// SeminareListingView.m
// Seminar App2
//
// Created by Alexander Frischbutter on 05.07.11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "SeminareListingView.h"
//#import "SeminareView.h"
#implementation SeminareListingView
#synthesize SeminareListeTabelle, selectedSeminar, seminareArray, receivedData;
- (void) parseData:(NSString *)url
{
if(receivedData)
{
receivedData = nil;
}
NSLog(#"Parsing... url: %#", url);
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#", url]] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if(theConnection)
{
receivedData = [[NSMutableData data] retain];
}
else
{
//label.text = #"XML nicht geladen";
NSLog(#"XML nicht gefunden");
}
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
SeminareListeTabelle = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] style:UITableViewStylePlain];
SeminareListeTabelle.delegate = self;
SeminareListeTabelle.dataSource = self;
SeminareListeTabelle.autoresizesSubviews = YES;
seminareArray = [[NSMutableArray alloc] init];
self.seminareArray = [NSMutableArray arrayWithCapacity:10];
[self parseData:[NSString stringWithFormat:#"http://akademie.kunden.fincha.com/semapp/sem_kat_arbtechnik.xml", selectedSeminar]];
self.navigationItem.title = [NSString stringWithFormat:#"%#", selectedSeminar];
self.view = SeminareListeTabelle;
// Do any additional setup after loading the view from its nib.
}
- (void)startParsingData
{
NSLog(#"Parsing started");
NSXMLParser *dataParser = [[NSXMLParser alloc] initWithData:receivedData];
dataParser.delegate = self;
[dataParser parse];
[dataParser release];
[receivedData release];
NSLog(#"Received Data in seminareArray");
/*
for(int i = 0; i < [seminareArray count]; i++)
{
NSLog(#"%d is %#", i, [seminareArray objectAtIndex:i]);
//NSLog(#"Count %d", [kategorienArray count]);
}
*/
//[seminareArray release];
NSLog(#"Reload data in TableView");
[self.SeminareListeTabelle reloadData];
NSLog(#"Data reloaded");
}
- (void)viewDidUnload
{
//[seminareArray release];
//[SeminareListeTabelle release];
NSLog(#"Vew unloaded");
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *SimpleTableIdentifier = #"SimpleTableIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: SimpleTableIdentifier];
if (cell == nil) { cell = [[[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:SimpleTableIdentifier] autorelease];
}
if([seminareArray count] != 0)
{
NSLog(#"Adding data to cell");
NSUInteger row = [indexPath row];
//cell.textLabel.text = [NSString stringWithFormat:#"bla, %d", row]; //[seminareArray objectAtIndex:row];
cell.textLabel.text = [seminareArray objectAtIndex:row];
NSLog(#"Added data to cell");
}
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//NSLog(#"Count %d", [self.seminareArray count]);
return [seminareArray count];
}
-(NSInteger) tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 0;
}
//Anzeige mit Seminaren öffnen bei Click auf die Zeile
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//gehe zurück zum ersten View
//NSLog(#"Received Data in seminareArray");
[[self navigationController] popViewControllerAnimated:YES];
}
- (void)connection:(NSURLConnection *)connection didReceiveResonse:(NSURLResponse *)response
{
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
if(receivedData)
{
[receivedData appendData:data];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[connection release];
[receivedData release];
//label.text = #"Connection failed";
NSLog(#"Verbindung fehlgeschlagen!");
//[[self navigationController] popViewControllerAnimated:YES];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self startParsingData];
[connection release];
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
//NSLog(#"Parser was called. Element: %#", elementName);
if([elementName isEqualToString:#"seminar"])
{
//NSLog(#"%#", [attributeDict objectForKey:#"name"]);
NSString *seminarName = [NSString stringWithFormat:#"%#", [attributeDict objectForKey:#"name"]];
[seminareArray addObject:seminarName];
[seminarName release];
}
}
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
NSLog(#"Parse Error %#", parseError);
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
The problem stems from this code:
seminareArray = [[NSMutableArray alloc] init]; // owned
seminareArray = [NSMutableArray arrayWithCapacity:10]; // autoreleased
You're first initializing the semiareArray as an owned object, but then are re-setting it as an autoreleased object.
Meaning, it will be released after the run-loop terminates.
Remove the second (autoreleased) statement but keep the first, and everything should work fine.
The reason why you're getting the EXC_BAD_ACCESS error is because the seminareArray object is released at some point before it being used again.
Additionally, try to debug your
cell.textLabel.text = [seminareArray objectAtIndex:row];
Try setting it as id var = [seminareArray objectAtIndex:row]; and then setting cell.textLabel.text = var; This will tell you if the error occurs due to array being dealloc'd too early, or improper cell/textLabel.
Updated:
There's an additional problem is with the code:
NSString *seminarName = [NSString stringWithFormat:#"%#", [attributeDict objectForKey:#"name"]];
[seminareArray addObject:seminarName];
[seminarName release]; // <--
You're creating an auto-released object seminarName, which technically has retain count 0. You're adding it to the semiareArray, which ups the object retain count to 1. Then you're releasing it again. Which causes it to be dealloc'd at runtime. The problem is that when you're assigning the value to the textLabel, the object no longer exists.
Solution: remove the [seminarName release]; Don't worry about releasing the seminarName, since it's auto-released, it will be released when the array is dealloc'd, or when the object it removed from the array.
David's answer is correct but I would advice reading up on your memory management.
If you are synthesizing properties then it is a lot easier to use the getters and setters and let them do the memory management for you. The exception being in your init/dealloc methods where you should try to directly use the ivars to avoid any potential side effects of using the getters/setters.
With the two lines david highlighted
seminareArray = [[NSMutableArray alloc] init]; // owned
seminareArray = [NSMutableArray arrayWithCapacity:10]; // autoreleased
You could potentially use either if the memory management was done correctly.
The first line on its own is correct as it creates an instance of NSMutableArray with a retain count of +1 then assigns it straight to the ivar.
Then as David pointed out the second line replaces this with an autoreleased NSMutableArray so this line is superflous and crashes your program. The method arrayWithCapacity: is not simply setting the capacity of the array it is giving you a new autoreleased array.
If you wanted to use an autoreleased NSMutableArray then you would need to use the setter either with dot notation of passing a message:
self.seminareArray = [NSMutableArray arrayWithCapactiy:10];
OR
[self setSeminareArray:[NSMutableArray arrayWithCapcity:10]];
By simply referencing things straight to seminareArray you are avoiding the getters/setters you synthesized and therefore are responsible for all of your memory management.
A hint at a memory leak:
self.seminareArray = [[NSMutableArray alloc] init];
this will leak memory because seminare is declared as retained property:
#property (nonatomic, retain) NSMutableArray *seminareArray;
This is not, anyway, the cause of your other issue.
The error you are having is caused by row being greater that the count of your array. So either you don't add sufficient objects to the array, or you try to use an incorrect value for row. Inspect that line with a debugger and ensure that row never goes beyond [seminare count]; you will find out easily why it happens.
Everything works fine when the view that holds my table is the main (first) view.
However, when it's not the first view and I switch into that view, my table does not load data and I get an empty table.
Using NSLog I can tell that the program is not invoking numberOfRowsInSection and cellForRowAtIndexPath.
I have <UITableViewDataSource, UITableViewDelegate>, IBOutlet UITableview *tableView all declared. They are also connected in the InterfaceBuilder.
I tried using viewWillAppear and [tableView reloadData] but that did not help.
I'm new to iPhone development and your help is appreciated!
UPDATE:
I tried [tableView reloadData] but nothing happened.
I'm not releasing tableView anywhere but dealloc.
Here is some code:
appDelegate
//
// tpbAppDelegate.m
// tpb
#import "listController.h"
#import "tpbAppDelegate.h"
#import "tpbViewController.h"
#implementation tpbAppDelegate
#synthesize window;
#synthesize viewController;
#synthesize navController;
#synthesize toolbar;
#synthesize btnMyLoc;
#synthesize places; //array that holds data from the XML file
- (void)applicationDidFinishLaunching:(UIApplication *)application {
//add places into an array that can be used by other views (Table)
rssList = [[NSMutableArray alloc] initWithCapacity:1];
NSString *paths = [[NSBundle mainBundle] resourcePath];
NSString *xmlFile = [paths stringByAppendingPathComponent:#"tourplay.xml"];
NSURL *xmlURL = [NSURL fileURLWithPath:xmlFile isDirectory:NO];
NSLog(#"DATA URL: %#", xmlURL);
NSXMLParser *firstParser = [[NSXMLParser alloc] initWithContentsOfURL:xmlURL];
[firstParser setDelegate:self];
[firstParser parse];
// Resize window for toolbar:
CGRect frame = viewController.view.frame;
frame.size.height -= toolbar.frame.size.height;
viewController.view.frame = frame;
//[window addSubview:viewController.view];
navController.viewControllers = [NSArray arrayWithObject:viewController];
navController.view.frame = frame;
[window addSubview:navController.view];
[window makeKeyAndVisible];
}
- (IBAction)showList:(id)sender{
//Switch to table view on segment control change
UISegmentedControl *segmentControl = (UISegmentedControl *)sender;
NSString *curSelection = [NSString stringWithFormat:#"%d", [segmentControl selectedSegmentIndex]];
//[segmentControl titleForSegmentAtIndex: [segmentControl selectedSegmentIndex]]];
NSLog(#"pressed button %#", curSelection);
if ([curSelection isEqualToString:#"1"]){
NSLog(#"TABLE SELECTED");
listController *listTable = [[listController alloc] init];
[navController pushViewController:listTable animated:YES]; //SWITCH TO TABLE VIEW (listController)
} else {
tpbViewController *tpb = [[tpbViewController alloc] init];
NSLog(#"MAP SELECTED");
[navController pushViewController:tpb animated:YES];
}
}
#pragma mark Praser Methods
//Parse XML into an array
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict {
//NSLog(#"Started parsing");
if ([elementName compare:#"tour_point"] == NSOrderedSame) {
[self.places addObject:[[NSDictionary alloc] initWithObjectsAndKeys:
//[attributeDict objectForKey:#"tour_point_id"],#"tour_point_id",
[attributeDict objectForKey:#"name"],#"name",
[attributeDict objectForKey:#"tour_html"],#"tour_html",
[attributeDict objectForKey:#"audio_src"],#"audio_src",
nil]];
} else if ([elementName compare:#"title"] == NSOrderedSame) {
titlename = (NSString *)[attributeDict objectForKey:#"titlename"];
NSLog(#"Done parsing %# points", titlename);
}
}
- (void)parserDidEndDocument:(NSXMLParser *)parser {
NSLog(#"Parser end");
[parser release];
}
- (void)dealloc {
[viewController release];
[rssList release];
[places release];
[toolbar release];
[window release];
[super dealloc];
}
#end
listController.h - table class
#import <UIKit/UIKit.h>
#interface listController : UIViewController <UITableViewDataSource, UITableViewDelegate> {
UITableView *tableView;
NSMutableArray *places;
NSString *titlename;
}
#property (nonatomic, retain) IBOutlet UITableView *tableView;
#end
listController.m - table implemenation
//
// listController.m
// tpb
//
//
#import "listController.h"
#import "tpbAppDelegate.h"
#implementation listController
#synthesize tableView;
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
//- (void)viewWillAppear {
tpbAppDelegate *delegate = (tpbAppDelegate *)[[UIApplication sharedApplication] delegate];
places = delegate.places;
NSLog(#"Loaded table view");
[super viewDidLoad];
// [tableView reloadSectionIndexTitles];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tv {
NSLog(#"Number of secions");
return 1;
}
- (NSInteger)tableView:(UITableView *)tv numberOfRowsInSection:(NSInteger)section {
NSLog(#"GETTING COUNT");
return [places count];
}
- (UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Assigning Cells");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tv dequeueReusableCellWithIdentifier:CellIdentifier];
if (nil == cell) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.text = [[places objectAtIndex:indexPath.row] objectForKey:#"name"];
//NSLog(#"count %#", [[places objectAtIndex:indexPath.row] objectForKey:#"name"]);
//cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
- (void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"ROW clicked");
[tv deselectRowAtIndexPath:indexPath animated:YES];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
NSLog(#"Unloaded tableview");
}
- (void)dealloc {
[tableView release];
[places release];
[super dealloc];
}
#end
-- So far, I know that viewDidLoad loads, but the table meathods such as cellRowAtIndexPath are not invoked.
This problem happens when you do not link the datasource and delegate of table view to the file's owner in the interface builder. Cross check once again in the interface builder see the connections for datasource and delegate of tableview are properly made with files owner and you have not mistakenly linked them with view.
Besides tell one thing more if
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
is getting called. If it is then you have passed 0 in this. Well a '0' is not acceptable from iphone-sdk-3.0 and later.
Thanks,
Madhup
- (IBAction)showList:(id)sender{
//Switch to table view on segment control change
UISegmentedControl *segmentControl = (UISegmentedControl *)sender;
NSString *curSelection = [NSString stringWithFormat:#"%d", [segmentControl selectedSegmentIndex]];
//[segmentControl titleForSegmentAtIndex: [segmentControl selectedSegmentIndex]]];
NSLog(#"pressed button %#", curSelection);
if ([curSelection isEqualToString:#"1"]){
NSLog(#"TABLE SELECTED");
listController *listTable = [[listController alloc] init];
//Try this
listTable.places = self.places; // set the array contents here and check
[navController pushViewController:listTable animated:YES]; //SWITCH TO TABLE VIEW (listController)
} else {
tpbViewController *tpb = [[tpbViewController alloc] init];
NSLog(#"MAP SELECTED");
[navController pushViewController:tpb animated:YES];
}
}
Dont forget to write the accessors in ListController for places array. And let me know what happens.
When you go to display your tableview, call the reloadData method on it; if your datasource/delegate is assigned to the correct object then that will trigger the table view to ask the datasource/delegate for its cells, and it should display at that point.