Memory Leaks in for loop (Objective-C iPhone)
I'm getting quite a few memory leaks with the loop in my - (void)connectionDidFinishLoading:(NSURLConnection *)connection
method. Just wondering whether anyone can lead me in the right direction as to how to decrease the amount of memory leaks occurring? Perhaps it isn't the best code... any help with other areas would be greatly appreciated.
// SearchViewController.h
#import <UIKit/UIKit.h>
#import "VenueDetailViewController.h"
#import "OverlayViewController.h"
#import "TBXML.h"
#import "CoreLocationController.h"
@interface SearchViewController : UIViewController <UITableViewDelegate, UISearchBarDelegate, CoreLocationControllerDelegate> {
VenueDetailViewController *venueDetailView;
IBOutlet UITableView *tv;
NSString *navBarTitle;
NSMutableArray *venues;
NSMutableArray *primaryCategories;
NSString *categoryId;
//Search properties.
OverlayViewController *overlayView;
IBOutlet UISearchBar *searchBar;
BOOL letUserSelectRow;
//Core location properties.
CoreLocationController *CLController;
BOOL searching;
NSMutableData *responseData;
}
@property (nonatomic, retain) NSString *navBarTitle;
@property (nonatomic, retain) CoreLocationController *CLController;
@property (nonatomic, retain) VenueDetailViewController *venueDetailView;
@property (nonatomic, retain) OverlayViewController *overlayView;
@property (nonatomic, retain) IBOutlet UITableView *tv;
@property (nonatomic, retain) NSMutableArray *venues;
@property (nonatomic, retain) NSMutableArray *primaryCategories;
@property (nonatomic, retain) NSString *categoryId;
- (void)doneSearching_Clicked:(id)sender;
- (void)findLocations:(id)sender;
- (void)loadPlacesWithLat:(NSString *)lat andLong:(NSString *)lng;
- (void)findPostcode:(NSString *)postcode;
- (void)showReloadButton;
@end
// SearchViewController.m
#import "SearchViewController.h"
#import "GenericCell.h"
#import "FSVenue.h"
#import "AsyncImageView.h"
#import "Helper.h"
#import "JSON.h"
@implementation SearchViewController
@synthesize tv;
@synthesize venueDetailView, overlayView;
@synthesize CLController;
@synthesize navBarTitle;
@synthesize venues, primaryCategories;
@synthesize categoryId;
- (void)viewDidLoad {
//Set the title.
navBarTitle = @"Nearby Places";
self.title = navBarTitle;
//Set background and border to clear (to allow for background image to be visible).
tv.backgroundColor = [UIColor clearColor];
[tv setSeparatorColor:[UIColor clearColor]];
//Add the search bar.
tv.tableHeaderView = searchBar;
searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
letUserSelectRow = YES;
venues = [[NSMutableArray alloc] init];
primaryCategories = [[NSMutableArray alloc] init];
//Core location init.
CLController = [[CoreLocationController alloc] init];
CLController.delegate = self;
//Add a refresh icon to the top right navigation bar.
[self showReloadButton];
if (self.categoryId != nil) {
[self findLocations:nil];
}
searching = NO;
[super viewDidLoad];
}
- (void)showReloadButton {
UIBarButtonItem *refreshItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh
target:self
action:@selector(findLocations:)];
self.navigationItem.rightBarButtonItem = refreshItem;
[refreshItem release];
}
#pragma mark -
#pragma mark Nearby Places / Core Location
- (void)findLocations:(id)sender {
// Display loading overlay view.
if (!searching) {
[Helper beginLoading:self.view withTitle:navBarTitle];
[self doneSearching_Clicked:nil];
//Calls locationUpdate delegate method.
[CLController.locMgr startUpdatingLocation];
searching = YES;
}
}
- (void)locationUpdate:(CLLocation *)location {
NSString *lat;
NSString *lng;
#if !(TARGET_IPHONE_SIMULATOR)
lat = [NSString stringWithFormat:@"%f", location.coordinate.latitude];
lng = [NSString stringWithFormat:@"%f", location.coordinate.longitude];
#else
lat = @"-37.816016";
lng = @"144.969717";
#endif
[self loadPlacesWithLat:lat andLong:lng];
}
- (void)locationError:(NSError *)error {
NSLog(@"locationError: %@", [error description]);
}
- (void)loadPlacesWithLat:(NSString *)lat andLong:(NSString *)lng {
[CLController.locMgr stopUpdatingLocation];
responseData = [[NSMutableData data] retain];
NSString *url = [NSString stringWithFormat:@"https://api.foursquare.com/v1/venues.json?geolat=%@&geolong=%@", lat, lng];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)findPostcode:(NSString *)pcode {
//Webservice URL: http://ws.geonames.org/findNearbyPostalCodes?postalcode=2000&country=AU&style=SHORT&maxRows=1
NSString *suburb;
NSString *postcode;
NSString *lat1;
NSString *lng1;
// load and parse an xml string.
TBXML* tbxml = [[TBXML alloc] initWithURL:[NSURL URLWithString:
[NSString stringWithFormat:@"http://ws.geonames.org/findNearbyPostalCodes?postalcode=%@&country=AU&style=SHORT&maxRows=1",
pcode]]];
// obtain root element.
TBXMLElement *root = tbxml.rootXMLElement;
// if root element is valid.
if (root) {
// search for the first geonames element within the root elements children.
TBXMLElement *code = [TBXML childElementNamed:@"code" parentElement:root];
if (code != nil) {
// find the lat child element of the code element.
TBXMLElement *lat = [TBXML childElementNamed:@"lat" parentElement:code];
if (lat != nil) {
lat1 = [TBXML textForElement:lat];
}
// find the long child element of the code element.
TBXMLElement *lng = [TBXML childElementNamed:@"lng" parentElement:code];
if (lng != nil) {
lng1 = [TBXML textForElement:lng];
}
// find the postalcode child element of the code element.
TBXMLElement *postalcode = [TBXML childElementNamed:@"postalcode" parentElement:code];
if (postalcode != nil) {
postcode = [TBXML textForElement:postalcode];
}
// find the postalcode child element of the code element.
TBXMLElement *name = [TBXML childElementNamed:@"name" parentElement:code];
if (name != nil) {
suburb = [TBXML textForElement:name];
}
NSLog(@"Searching Postcode %@ (%@) ...", postcode, suburb);
NSLog(@" Lat - %@", lat1);
NSLog(@" Long - %@", lng1);
[self loadPlacesWithLat:lat1 andLong:lng1];
}
}
// release resources
[tbxml release];
}
#pragma mark -
#pragma mark JSON Over HTTP
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[responseData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"connectin didFailWithError: %@", [error description]);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
//[responseData release];
NSDictionary *dictionary = [responseString JSONValue];
[responseString release];
NSArray *venueArray = [[[dictionary valueForKeyPath:@"groups"] objectAtIndex:0] valueForKeyPath:@"venues"];
if ([dictionary valueForKeyPath:@"error"] != nil) {
[Helper displayAlertMessage:[dictionary valueForKeyPath:@"error"] withTitle:@"Foursquare"];
}
for (id result in venueArray) {
FSVenue *venue = [[FSVenue alloc] init];
venue.name = [result valueForKeyPath:@"name"];
venue.venueId = [result valueForKeyPath:@"id"];
venue.geoLat = [result valueForKeyPath:@"geolat"];
venue.geoLong = [result valueForKeyPath:@"geolong"];
NSDictionary *primaryCategoryDict = [result valueForKeyPath:@"primarycategory"];
FSPrimaryCategory *primaryCategory = [[FSPrimaryCategory alloc] init];
primaryCategory.iconUrl = [primaryCategoryDict valueForKeyPath:@"iconurl"];
primaryCategory.iconUrl = [primaryCategory.iconUrl stringByReplacingOccurrencesOfString:@".png" withString:@"_64.png"];
primaryCategory.nodeName = [primaryCategoryDict valueForKeyPath:@"nodename"];
primaryCategory.primaryCategoryId = [NSString stringWithFormat:@"%@", [primaryCategoryDict valueForKeyPath:@"id"]];
//Check if categories match the category selected from the FSCategory controllers.
if (self.categoryId != nil) {
if ([self.categoryId isEqualToString:primaryCategory.primaryCategoryId]) {
[venues addObject:venue];
[venue release];
[primaryCategories addObject:primaryCategory];
[primaryCategory release];
} else {
[venue release];
[primaryCategory release];
}
} else {
[venues addObject:venue];
[venue release];
[primaryCategories addObject:primaryCategory];
[primaryCategory release];
}
}
[tv reloadData];
//Hide loading overlay view.
[Helper finishLoading:navBarTitle];
searching = NO;
}
#pragma mark -
#pragma mark Table View
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [venues count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"GenericCell";
GenericCell *cell = (GenericCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:nil options:nil];
for (id currentObject in topLevelObjects) {
if ([currentObject isKindOfClass:[UITableViewCell class]]) {
cell = (GenericCell *)currentObject;
break;
}
}
} else {
AsyncImageView *oldImage = (AsyncImageView *)
[cell.contentView viewWithTag:999];
[oldImage removeFromSuperview];
}
FSPrimaryCategory *primaryCategory = (FSPrimaryCategory *)[primaryCategories objectAtIndex:indexPath.row];
FSVenue *venue = (FSVenue *)[venues objectAtIndex:indexPath.row];
AsyncImageView *asyncImage = [[[AsyncImageView alloc] initWithFrame:CGRectMake(3, 3, 48, 48)] autorelease];
asyncImage.tag = 999;
NSURL *url = [NSURL URLWithString:primaryCategory.iconUrl];
[asyncImage loadImageFromURL:url];
[cell.contentView addSubview:asyncImage];
//The two images are 1x140 vertical gradients that UIKit automatically stretches horizontally to fit the width of the cell.
cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Cell_1x140.png"]];
cell.selectedBackgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"CellSelected_1x140.png"]];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.titleLabel.text = venue.name;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (venueDetailView == nil) {
venueDetailView = [[VenueDetailViewController alloc] initWithNibName:@"VenueDetailViewController" bundle:[NSBundle mainBundle]];
FSVenue *venue = (FSVenue *)[venues objectAtIndex:indexPath.row];
venueDetailView.vid = venue.venueId;
[self.navigationController pushViewController:venueDetailView animated:YES];
}
venueDetailView = nil;
[venueDetailView release];
}
- (NSIndexPath *)tableView :(UITableView *)theTableView wi开发者_StackOverflowllSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (letUserSelectRow)
return indexPath;
else
return nil;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row % 2) {
[cell setBackgroundColor:[UIColor colorWithRed:((float)173 / 255.0f) green:((float)173 / 255.0f) blue:((float)176 / 255.0f) alpha:.60]];
} else {
[cell setBackgroundColor:[UIColor colorWithRed:((float)152 / 255.0f) green:((float)152 / 255.0f) blue:((float)156 / 255.0f) alpha:.60]];
}
cell.selectionStyle = UITableViewCellSelectionStyleGray;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
NSString *titleHeader;
if ([venues count] == 0) {
titleHeader = @"No venues were found.";
} else {
titleHeader = @"";
}
return titleHeader;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 55;
}
#pragma mark -
#pragma mark Search Bar
- (void)searchBarTextDidBeginEditing:(UISearchBar *)theSearchbar {
//Add the overlay view.
if (overlayView == nil)
overlayView = [[OverlayViewController alloc] initWithNibName:@"OverlayViewController" bundle:[NSBundle mainBundle]];
CGFloat yaxis = self.navigationController.navigationBar.frame.size.height;
CGFloat width = self.view.frame.size.width;
CGFloat height = self.view.frame.size.height;
//Parameters x = origin on x-axis, y = origin on y-axis.
CGRect frame = CGRectMake(0, yaxis, width, height);
overlayView.view.frame = frame;
overlayView.view.backgroundColor = [UIColor grayColor];
overlayView.view.alpha = 0.5;
overlayView.searchView = self;
[tv insertSubview:overlayView.view aboveSubview:self.parentViewController.view];
letUserSelectRow = NO;
tv.scrollEnabled = NO;
}
- (BOOL)searchBarShouldBeginEditing:(UISearchBar *)theSearchBar {
searchBar.showsScopeBar = YES;
[searchBar sizeToFit];
[searchBar setShowsCancelButton:YES animated:YES];
return YES;
}
- (BOOL)searchBarShouldEndEditing:(UISearchBar *)theSearchBar {
searchBar.showsScopeBar = NO;
[searchBar sizeToFit];
[searchBar setShowsCancelButton:NO animated:YES];
[self doneSearching_Clicked:nil];
return YES;
}
- (void) doneSearching_Clicked:(id)sender {
[searchBar resignFirstResponder];
letUserSelectRow = YES;
tv.scrollEnabled = YES;
[overlayView.view removeFromSuperview];
[overlayView release];
overlayView = nil;
//Reverse geocode postcode entered.
if (![searchBar.text isEqualToString:@""]) {
[self findPostcode:searchBar.text];
searchBar.text = @"";
[tv reloadData];
}
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)theSearchBar {
[self doneSearching_Clicked:nil];
}
- (void) searchBarSearchButtonClicked:(UISearchBar *)theSearchBar {
[searchBar resignFirstResponder];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[tv reloadData];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)viewDidUnload {
[super viewDidUnload];
}
- (void)dealloc {
[navBarTitle release];
[venueDetailView release];
[CLController release];
[tv release];
[venues release];
[primaryCategories release];
[categoryId release];
[responseData release];
[super dealloc];
}
@end
If self.categoryId != nil
and ![self.categoryId isEqualToString:primaryCategory.primaryCategoryId]
, then primaryCategory
and venue
are leaked. I'd just factor [primaryCategory release]
(and the same for venue
) out of the branches, and put it at the end of the loop.
For future help, you might like XCode's "Build and Analyse" mode, which should statically detect this kind of code flow leak and tell you exactly what allocation is leaked where.
You are leaking the venue
and primaryCategory
whenever self.categoryId
is set but does not match primaryCategory.primaryCategoryId
.
Your code could certainly be cleaned up:
- Add an
-[FSVenue initWithResult:]
method. - Add an
-[FSPrimaryCategory initWithDictionary:
] method. - Decide whether to use a high-resolution icon based on the device.
- The
if
statements at the end of the loop contain duplicated code. Consider doing this instead:
for (...) { ... //Check if categories match the category selected from the FSCategory controllers. if (self.categoryId && ![self.categoryId isEqualToString:primaryCategory.primaryCategoryId]) { [venue release]; [primaryCategory release]; continue; } [venues addObject:venue]; [venue release]; [primaryCategories addObject:primaryCategory]; [primaryCategory release]; }
- Consider using
autorelease
whenever you create an object. Then you would not have leaked anything in the first place!
After the discussion we add in Jeremy's answer, here's a suggestion of code.
In your object's .h file
@interface MyObject : UIViewController <UITableViewDelegate,UITableViewDataSource> {
NSMutableArray *venues;
NSMutableArray *primaryCategories;
}
@property (nonatomic,retain) NSMutableArray *venues;
@property (nonatomic,retain) NSMutableArray *primaryCategories;
In your object's .m file
@implementation MyObject
@synthesize venues;
@synthesize primaryCategories;
- (void)viewDidLoad {
[super viewDidLoad];
self.venues = [[NSMutableArray alloc] init];
self.primaryCategories = [[NSMutableArray alloc] init];
}
- (void)whatEverMethodYouLike {
// The for loop here (with proper deallocs)
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
FSPrimaryCategory *primaryCategory = (FSPrimaryCategory *)[primaryCategories objectAtIndex:indexPath.row];
// Et caetera
}
- (void)dealloc {
[self.venues release];
[self.primaryCategories release];
[super dealloc];
}
venues = [[NSMutableArray alloc] init];
primaryCategories = [[NSMutableArray alloc] init];
for (id result in venueArray) {
FSVenue *venue = [[FSVenue alloc] init];
venue.name = [result valueForKeyPath:@"name"];
venue.venueId = [result valueForKeyPath:@"id"];
venue.geoLat = [result valueForKeyPath:@"geolat"];
venue.geoLong = [result valueForKeyPath:@"geolong"];
NSDictionary *primaryCategoryDict = [result valueForKeyPath:@"primarycategory"];
FSPrimaryCategory *primaryCategory = [[FSPrimaryCategory alloc] init];
primaryCategory.iconUrl = [primaryCategoryDict valueForKeyPath:@"iconurl"];
primaryCategory.iconUrl = [primaryCategory.iconUrl stringByReplacingOccurrencesOfString:@".png" withString:@"_64.png"];
primaryCategory.nodeName = [primaryCategoryDict valueForKeyPath:@"nodename"];
primaryCategory.primaryCategoryId = [NSString stringWithFormat:@"%@", [primaryCategoryDict valueForKeyPath:@"id"]];
//Check if categories match the category selected from the FSCategory controllers.
if (self.categoryId != nil) {
if ([self.categoryId isEqualToString:primaryCategory.primaryCategoryId]) {
[venues addObject:venue];
[primaryCategories addObject:primaryCategory];
}
} else {
[venues addObject:venue];
[primaryCategories addObject:primaryCategory];
}
[primaryCategory release];
[venue release];
}
You're leaking venue and primaryCategory unless they meet the conditions to be inserted in the arrays. The correct structure of this loop is above. I take it that the arrays 'venues' and 'primaryCategories' are ivars and you release them in the dealloc method of your class. (or else you're leaking those arrays too.)
you have one leak primaryCategory , because inside the if statement u have another if statement .. so there might have possibilities of memory leak
精彩评论