Утечки памяти в течение цикла (Objective-C iPhone) - PullRequest
0 голосов
/ 03 декабря 2010

Я получаю довольно много утечек памяти с помощью цикла в моем методе - (void)connectionDidFinishLoading:(NSURLConnection *)connection.Просто интересно, может ли кто-нибудь привести меня в правильном направлении относительно того, как уменьшить количество происходящих утечек памяти?Возможно, это не лучший код ... любая помощь в других областях будет принята с благодарностью.

//  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 willSelectRowAtIndexPath:(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

Ответы [ 5 ]

5 голосов
/ 03 декабря 2010

Если self.categoryId != nil и ![self.categoryId isEqualToString:primaryCategory.primaryCategoryId], то primaryCategory и venue просочились. Я бы просто вычленил [primaryCategory release] (и то же самое для venue) из ветвей и поместил его в конец цикла.

Для получения справки в будущем вам может понравиться режим «Построить и проанализировать» в XCode, который должен статически обнаруживать такого рода утечки потока кода и точно указывать, где и где происходит утечка.

1 голос
/ 03 декабря 2010

После обсуждения мы добавляем в ответ Джереми вот предложение кода.

В файле вашего объекта .h

@interface MyObject : UIViewController <UITableViewDelegate,UITableViewDataSource> {
    NSMutableArray *venues;
    NSMutableArray *primaryCategories;
}

@property (nonatomic,retain) NSMutableArray *venues;
@property (nonatomic,retain) NSMutableArray *primaryCategories;

В файле вашего объекта .m

@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];
}
1 голос
/ 03 декабря 2010
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];
}

Вы пропускаете место проведения и первичную категорию, если они не соответствуют условиям, которые будут вставлены в массивы. Правильная структура этого цикла выше. Я полагаю, что массивы 'venues' и 'primaryCategories' - это ivars, и вы освобождаете их в методе dealloc вашего класса. (или вы тоже пропускаете эти массивы.)

1 голос
/ 03 декабря 2010

Вы пропускаете venue и primaryCategory всякий раз, когда self.categoryId установлен, но не соответствует primaryCategory.primaryCategoryId.

Ваш код, безусловно, может быть очищен:

  • Добавить метод -[FSVenue initWithResult:].
  • Добавить метод -[FSPrimaryCategory initWithDictionary:].
  • Решить, использовать ли значок с высоким разрешением в зависимости от устройства.
  • The if операторов в конце цикла содержат дублированный код.Попробуйте сделать это вместо этого:
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];
}
  • Попробуйте использовать autorelease всякий раз, когда вы создаете объект.Тогда бы ты ничего не просочился в первую очередь!
0 голосов
/ 03 декабря 2010

у вас есть одна утечка primaryCategory, потому что внутри оператора if у вас есть другой оператор if .. поэтому возможны утечки памяти

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...