简体   繁体   English

for循环中的内存泄漏(Objective-C iPhone)

[英]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. 我的- (void)connectionDidFinishLoading:(NSURLConnection *)connection方法中的循环- (void)connectionDidFinishLoading:(NSURLConnection *)connection内存泄漏。 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 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

If self.categoryId != nil and ![self.categoryId isEqualToString:primaryCategory.primaryCategoryId] , then primaryCategory and venue are leaked. 如果self.categoryId != nil![self.categoryId isEqualToString:primaryCategory.primaryCategoryId] ,则primaryCategoryvenue将泄漏。 I'd just factor [primaryCategory release] (and the same for venue ) out of the branches, and put it at the end of the loop. 我只是将[primaryCategory release] (与venue相同)从分支中[primaryCategory release]出来,并将其放在循环的末尾。

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. 为了获得将来的帮助,您可能喜欢XCode的“ Build and Analyse”模式,该模式应静态检测这种代码流泄漏并确切告诉您什么分配泄漏到哪里。

You are leaking the venue and primaryCategory whenever self.categoryId is set but does not match primaryCategory.primaryCategoryId . 您正在泄漏的venueprimaryCategory每当self.categoryId设置但不符合primaryCategory.primaryCategoryId

Your code could certainly be cleaned up: 您的代码当然可以清除:

  • Add an -[FSVenue initWithResult:] method. 添加-[FSVenue initWithResult:]方法。
  • Add an -[FSPrimaryCategory initWithDictionary: ] method. 添加-[FSPrimaryCategory initWithDictionary: ]方法。
  • Decide whether to use a high-resolution icon based on the device. 根据设备决定是否使用高分辨率图标。
  • The if statements at the end of the loop contain duplicated code. 循环末尾的if语句包含重复的代码。 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. 创建对象时,请考虑使用autorelease Then you would not have leaked anything in the first place! 这样,您一开始就不会泄漏任何东西!
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. 您泄漏的场所和primaryCategory除非它们符合要插入阵列的条件。 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. 我认为数组“ venues”和“ primaryCategories”是ivars,您可以在类的dealloc方法中释放它们。 (or else you're leaking those arrays too.) (否则,您也会泄漏这些数组。)

After the discussion we add in Jeremy's answer, here's a suggestion of code. 讨论之后,我们添加了Jeremy的答案,这是代码的建议。

In your object's .h file 在对象的.h文件中

@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 在对象的.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];
}

您有一个泄漏primaryCategory,因为在if语句中您有另一个if语句..因此可能存在内存泄漏的可能性

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM