【问题标题】:Memory Leaks in for loop (Objective-C iPhone)for循环中的内存泄漏(Objective-C iPhone)
【发布时间】:2010-12-03 14:31:22
【问题描述】:

我的- (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

【问题讨论】:

  • 你好。我想你应该先添加一个else语句以防你的self.categoryId不等于primaryCategoryId,然后释放你的场地/primaryCategory。

标签: iphone objective-c memory for-loop memory-leaks


【解决方案1】:

如果self.categoryId != nil![self.categoryId isEqualToString:primaryCategory.primaryCategoryId],则primaryCategoryvenue 被泄露。我只是将[primaryCategory release]venue)从分支中剔除,并将其放在循环的末尾。

为了将来的帮助,您可能会喜欢 XCode 的“构建和分析”模式,它应该静态检测这种代码流泄漏并告诉您确切的分配在哪里泄漏。

【讨论】:

    【解决方案2】:

    只要设置了self.categoryId 但与primaryCategory.primaryCategoryId 不匹配,您就会泄露venueprimaryCategory

    您的代码当然可以清理:

    • 添加一个-[FSVenue initWithResult:] 方法。
    • 添加一个-[FSPrimaryCategory initWithDictionary:] 方法。
    • 根据设备决定是否使用高分辨率图标。
    • 循环末尾的if 语句包含重复的代码。考虑改为这样做:
    为了 (...) { ... //检查类别是否与从 FSCategory 控制器中选择的类别匹配。 if (self.categoryId && ![self.categoryId isEqualToString:primaryCategory.primaryCategoryId]) { 【会场发布】; [主要类别发布]; 继续; } [场地添加对象:场地]; 【会场发布】; [primaryCategories addObject:primaryCategory]; [主要类别发布]; }
    • 在创建对象时考虑使用autorelease。那么你一开始就不会泄露任何东西!

    【讨论】:

    • 自动发布是一种懒惰(并且有潜在危险)的解决方案。不要考虑这一点:P 我同意其他观点。
    • 另外,我想知道如何才能最好地实现您的 -[FSVenue initWithResult:] 和 -[FSPrimaryCategory initWithDictionary:] 方法以满足我的需要。非常感谢为我的 if 语句提供解决方案。非常感谢。
    • 好的,场地和 primaryCategory 对象仍然存在内存泄漏。呃,这很糟糕。
    • 哦,我猜这部分是可选的。你现在的做法没有问题;杰里米的建议是在“完美世界”中做到这一点,但没有义务:)
    • 您应该在 for 循环之外同时释放场所和 primaryCategories
    【解决方案3】:

    经过讨论,我们添加了 Jeremy 的答案,这里有一个代码建议。

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

    【讨论】:

    • 感谢您的代码。这正是我已经在做的事情。唯一的区别是我的场所和primaryCategories 数组是在循环之前初始化的,而不是在viewDidLoad() 中初始化的。非常感谢您抽出宝贵时间提供代码。
    • 有一个问题,我从不使用“self”作为我的 ivars。这是不好的做法吗?
    • 不,没关系,每当我将我的变量定义为属性时,我习惯使用“self”,但取决于上下文:)
    • 好的,太好了。所以 - 我仍然遇到上述所有代码建议的内存泄漏。还有其他想法吗?我实际上已经回到了我原来的 for 循环(但当然没有在那里初始化数组)。
    • 嗯......我想整个代码会有所帮助:P 我想这都是关于 for 循环的,我不知道你究竟遵循了什么建议。
    【解决方案4】:
    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 方法中释放它们。 (否则你也会泄露这些数组。)

    【讨论】:

    • 这是正确的。我在我班级的 dealloc 方法中释放它们,因为它们是 ivars。
    • 我已经将两个数组的 init 移动到 viewDidLoad() 而不是在 for 循环之前。
    • 哎哟。通过您的 for 循环,我得到: *** -[FSPrimaryCategory iconUrl]: message sent to deallocated instance 0x98f39e0
    【解决方案5】:

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

    【讨论】:

    • Justicepenny:你能详细说明一下吗?在 if 语句中有另一个 if 语句有什么问题,以及如何“可能”因此存在内存泄漏?但是,在将 primaryCategory 添加到 primaryCategories 数组后,我会释放它吗?
    • if ([self.categoryId isEqualToString:primaryCategory.primaryCategoryId]) { [venues addObject:venue]; 【会场发布】; [primaryCategories addObject:primaryCategory]; [主要类别发布]; }
    • put [primaryCategory release];在 if else 语句之外
    • 废话。我点击了向下而不是向上。对不起!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-29
    • 1970-01-01
    • 2017-06-06
    • 1970-01-01
    • 1970-01-01
    • 2011-06-26
    • 2011-02-24
    相关资源
    最近更新 更多