【问题标题】:IOS NSString Memory LeakIOS NSString 内存泄漏
【发布时间】:2014-06-06 01:54:50
【问题描述】:

您好,我无法找出内存泄漏的原因,我开始怀疑它是否可能是某种 IOS 错误?泄漏几乎是随机出现的,调试器工具中泄漏的堆栈跟踪显示没有我物理编码的方法?泄漏仪器屏幕截图如下!谢谢!

这是第一个可能的罪魁祸首!

#import "BreakfastViewController.h"
#import "AppDelegate.h"
#import "CustomCell.h"
#import "Recipe.h"
#import "RecipeAzure.h"
#import "DetailViewController.h"

@interface BreakfastViewController ()

@end

@implementation BreakfastViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];


    // Do any additional setup after loading the view.
    _tv.dataSource = self;
    _tv.delegate = self;
    _ai = [[AzureInteraction alloc] initAzureInteraction];
    _ai.delegate = self;
    _searchBar.delegate = self;

    self.ai.busyUpdate = ^(BOOL busy)
    {
        if (busy)
        {

        } else
        {

        }
    };

    [_ai getBreakfasts:nil];


}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}


-(void)update
{


}
-(BOOL)canBecomeFirstResponder {
    return YES;
}

-(void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self becomeFirstResponder];
}

- (void)viewWillDisappear:(BOOL)animated {
    [self resignFirstResponder];
    [super viewWillDisappear:animated];
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
    if (motion == UIEventSubtypeMotionShake)
    {
        [_ai getBreakfasts:nil];


        NSLog(@"SHAKE");
    }
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSLog(@"breakfast sections   %d",[_ai breakfastCount]);
   NSInteger c = [_ai breakfastCount];
    return c;
}



- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
      NSLog(@"breakfast sections   %d",[_ai breakfastCount]);
    RecipeAzure *r = [_ai getBreakfastAtIndex:indexPath];
    CustomCell *c = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
    [c.imageView setContentMode:UIViewContentModeScaleAspectFit];
    if(r.img64 != nil)
    {
        NSString *str = r.img64;
        NSData *data = [[NSData alloc] initWithBase64EncodedString:str options:0];

        c.RecipeImage.image = [UIImage imageWithData:data];
    }
    c.RecipeTitle.text = r.title;
    c.IngrediantsLabel.text = r.ingrediants;
    return c;
}

-(void)userFinished:(NSString*)title:(NSString*)ingrediants:(NSString*)method:(NSString*)password:
(NSString*)image
{
    RecipeAzure *createdRecipe = [[RecipeAzure alloc]init];

    createdRecipe.title = title;
    createdRecipe.ingrediants = ingrediants;
    createdRecipe.method = method;
    createdRecipe.password = password;
    createdRecipe.img64 = image;
    createdRecipe.type = @"breakfast";
    NSLog(@"GT HERE");
    [_ai uploadRecipe:createdRecipe];

}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{

    if ([[segue identifier] isEqualToString:@"createBreakfast"])
    {

        CreateRecipeViewController *crvc = [segue destinationViewController];


        crvc.delegate = self;
    }
    if ([[segue identifier] isEqualToString:@"detailbreakfast"])
    {

        DetailViewController *dvc = [segue destinationViewController];

        NSIndexPath *ind = [_tv indexPathForSelectedRow];
        NSLog(@"%@",[_ai getBreakfastAtIndex:ind]);
        [dvc setRecipe:[_ai getBreakfastAtIndex:ind]];
        [dvc setDelegate:self];
    }
}
-(void)updateTableview
{
    [_tv reloadData];
}
-(void)setEditChanges:(RecipeAzure *)recipe
{
    NSLog(@"OH MY GOOD %@",recipe.rid);

   [_ai updateRecipe:recipe];
}

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
    NSLog(@"SEARCH BEEN PRESSED");
    [_ai getBreakfasts:searchBar.text];
    [searchBar resignFirstResponder];
}
- (void)searchBarCancelButtonClicked:(UISearchBar *) searchBar
{
    [_ai getBreakfasts:nil];
    searchBar.text = @"";
    [searchBar resignFirstResponder];
}

@end

这是第二个可能的罪魁祸首!

#import "CreateRecipeViewController.h"
#import "Recipe.h"
#import "AppDelegate.h"
#import "BreakfastViewController.h"

@interface CreateRecipeViewController ()
{
    float textViewY;
    //NSString *uepassword;
}

@end

@implementation CreateRecipeViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    _Title.delegate = self;
    _Recipe.delegate = self;
    _Ingrediants.delegate = self;
    _Recipe.text = @"Enter Recipe Here...";

    _FinishedEditing.hidden = YES;
    [self.Image setContentMode:UIViewContentModeScaleAspectFit];

    if(_delegate == nil)
    {
        NSLog(@"WTF");
    }
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (BOOL)textViewShouldBeginEditing:(UITextView *)textField
{

    return YES;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    textField.text = @"";


}
- (void)textViewDidBeginEditing:(UITextView *)textView
{
    textView.text = @"";
     textViewY = textView.frame.origin.y;

    //If we begin editing on the text field we need to move it up to make sure we can still
    //see it when the keyboard is visible.
    //
    //I am adding an animation to make this look better
    if(textView == _Recipe)
    {
        _Title.hidden = YES;
        _Ingrediants.hidden = YES;
        _Image.hidden = YES;
        _AddImage.hidden = YES;
        _FinishedEditing.hidden = NO;


    [UIView beginAnimations:@"Animate Text Field Up" context:nil];
    [UIView setAnimationDuration:.3];
    [UIView setAnimationBeginsFromCurrentState:YES];

        textViewY = _RecipeView.frame.origin.y;


    _RecipeView.frame = CGRectMake(_RecipeView.frame.origin.x,
                               80 , //this is just a number to put it above the keyboard
                               _RecipeView.frame.size.width,
                               _RecipeView.frame.size.height);

    [UIView commitAnimations];
    }

}

- (void)textFieldDidEndEditing:(UITextField *)textField
{

    [textField resignFirstResponder];



}
- (void)textViewDidEndEditing:(UITextView *)textView
{
    [textView resignFirstResponder];
}




- (IBAction)finishedPressed:(id)sender {

      [_Recipe resignFirstResponder];

           [UIView beginAnimations:@"Animate Text Field Up" context:nil];
        [UIView setAnimationDuration:.3];
        [UIView setAnimationBeginsFromCurrentState:YES];

        _RecipeView.frame = CGRectMake(_RecipeView.frame.origin.x,
                                       textViewY ,
                                       _RecipeView.frame.size.width,
                                       _RecipeView.frame.size.height);

        [UIView commitAnimations];
        _Title.hidden = NO;
        _Ingrediants.hidden = NO;
        _Image.hidden = NO;
        _AddImage.hidden = NO;
}

- (IBAction)takePicture:(id)sender {

    UIImagePickerController *picker = [[UIImagePickerController alloc]init];
    picker.sourceType =  UIImagePickerControllerSourceTypePhotoLibrary;

    picker.allowsEditing = YES;
    picker.delegate = self;

    [self presentViewController:picker animated:YES completion:nil];



}

- (IBAction)resignKeyboard:(id)sender {

    [sender resignFirstResponder];
}
- (IBAction)saveButton:(id)sender {
    NSLog(@"HELLLOOO");



    UIAlertView * alert =[[UIAlertView alloc ] initWithTitle:@"Password" message:@"Enter a password to be able to edit this recipe in the future!" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles: nil];
    alert.alertViewStyle = UIAlertViewStyleSecureTextInput;
    [alert addButtonWithTitle:@"Enter"];
    [alert show];





   // uepassword = nil;

    _FinishedEditing.hidden = true;
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 1)
    {
        UITextField *password = [alertView textFieldAtIndex:0];
        //uepassword = password.text;

        //self.createdRecipe.title = _Title.text;
        //self.createdRecipe.ingrediants = _Ingrediants.text;
        //self.createdRecipe.method = _Recipe.text;
        //self.createdRecipe.img64 = _imagestring;
        //NSString *imageString;
        //if(_Image.image != nil)
        //{
          //  NSData *data = UIImagePNGRepresentation(_Image.image);
            //imageString = [data base64EncodedStringWithOptions:0];
        //}

       // [self.delegate userFinished:_Title.text :_Ingrediants.text :_Recipe.text :@"password" :imageString];

        [self performSegueWithIdentifier:@"back" sender:self];



    }
}

-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{

    UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage];
    self.Image.image = image;





    [self dismissViewControllerAnimated:YES completion:nil];
}



@end

AzureInteraction 类

#import "AzureInteraction.h"
#import "Recipe.h"
#import "RecipeAzure.h"


@implementation AzureInteraction
{

}




-(id)initAzureInteraction
{
    self = [super init];

    if(self)
    {





        self.client = [client clientWithFilter:self];
         self.table = [_client tableWithName:@"Recipe"];

         self.busyCount = 0;





        return self;

    }
    return nil;
}



-(NSMutableArray*)getBreakfasts:(NSString*)pred
{
    NSLog(@"yeah its doing this but ");

    NSPredicate *bpred;
   if(pred == nil)
   {

     bpred = [NSPredicate predicateWithFormat:@"type == 'breakfast'"];
   }
   else{

        bpred = [NSPredicate predicateWithFormat:@"type == 'breakfast' AND title == %@",pred];
   }


    [_table readWithPredicate:bpred completion:^(NSArray *items, NSInteger totalCount, NSError *error) {


        _breakfasts = [items mutableCopy];


        [_delegate updateTableview];

    }];

    return _breakfasts;





}
-(NSMutableArray*)getLunches:(NSString*)pred
{
    NSLog(@"yeah its doing this but ");
    NSLog(@"%@",_recipes);

    NSPredicate *bpred;
    if(pred == nil)
    {
    bpred = [NSPredicate predicateWithFormat:@"type == 'lunch'"];
    }
    else{
        bpred = [NSPredicate predicateWithFormat:@"type == 'lunch' AND title == %@",pred];
    }


    [_table readWithPredicate:bpred completion:^(NSArray *items, NSInteger totalCount, NSError *error) {


        _lunches = [items mutableCopy];


        [_delegate updateTableview];

    }];

    return _lunches;


}
-(NSMutableArray*)getDinners:(NSString*)pred
{
    NSLog(@"yeah its doing this but ");
    NSLog(@"%@",_recipes);
    NSPredicate *bpred;
    if(pred == nil)
    {
     bpred = [NSPredicate predicateWithFormat:@"type == 'dinner'"];
    }
    else{
        bpred = [NSPredicate predicateWithFormat:@"type == 'dinner' AND title == %@",pred];
    }


    [_table readWithPredicate:bpred completion:^(NSArray *items, NSInteger totalCount, NSError *error) {


        _dinners = [items mutableCopy];


        [_delegate updateTableview];

    }];

    return _dinners;


}
-(void)updateTable
{






}
- (void)busy:(BOOL)busy
{
    // assumes always executes on UI thread
    if (busy)
    {
        if (self.busyCount == 0 && self.busyUpdate != nil)
        {
            self.busyUpdate(YES);
        }
        self.busyCount ++;
    }
    else
    {
        if (self.busyCount == 1 && self.busyUpdate != nil)
        {
            self.busyUpdate(FALSE);
        }
        self.busyCount--;
    }
}

- (void)handleRequest:(NSURLRequest *)request
                 next:(MSFilterNextBlock)next
             response:(MSFilterResponseBlock)response
{
    // A wrapped response block that decrements the busy counter
    MSFilterResponseBlock wrappedResponse = ^(NSHTTPURLResponse *innerResponse, NSData *data, NSError *error)
    {
        [self busy:NO];
        NSLog(@"OK");

        response(innerResponse, data, error);
    };

    // Increment the busy counter before sending the request
    [self busy:YES];
    NSLog(@"HMM");
    next(request, wrappedResponse);
}

-(RecipeAzure*)getBreakfastAtIndex:(NSIndexPath*)indexPath
{
    NSDictionary *d = [_breakfasts objectAtIndex:indexPath.row];

    RecipeAzure* t = [[RecipeAzure alloc] init];

    t.rid = [d valueForKey:@"id"];

    t.title = [d valueForKey:@"title"];
    t.ingrediants = [d valueForKey:@"ingrediants"];
    t.method = [d valueForKey:@"method"];
    t.type = [d valueForKey:@"type"];
    t.img64 = [d valueForKey:@"imageencoded"];
    t.password = [d valueForKey:@"password"];


    return t;
}
-(RecipeAzure*)getLunchAtIndex:(NSIndexPath*)indexPath
{
    NSDictionary *d = [_lunches objectAtIndex:indexPath.row];

    RecipeAzure* t = [[RecipeAzure alloc] init];
    t.rid = [d valueForKey:@"id"];
    t.title = [d valueForKey:@"title"];
    t.ingrediants = [d valueForKey:@"ingrediants"];
    t.method = [d valueForKey:@"method"];
    t.img64 = [ d valueForKey:@"imageencoded"];
    t.password = [d valueForKey:@"password"];

    return t;
}
-(RecipeAzure*)getDinnerAtIndex:(NSIndexPath*)indexPath
{
    NSDictionary *d = [_dinners objectAtIndex:indexPath.row];

    RecipeAzure* t = [[RecipeAzure alloc] init];
    t.rid = [d valueForKey:@"id"];
    t.title = [d valueForKey:@"title"];
    t.ingrediants = [d valueForKey:@"ingrediants"];
    t.method = [d valueForKey:@"method"];
    t.img64 = [ d valueForKey:@"imageencoded"];
    t.password = [d valueForKey:@"password"];

    return t;
}
-(NSInteger)breakfastCount
{
    NSLog(@"COUNTING");
    return [_breakfasts count];
}
-(NSInteger)lunchCount
{
    return [_lunches count];
}
-(NSInteger)dinnerCount
{
    return [_dinners count];
}
-(void)uploadRecipe:(RecipeAzure*)recipe
{
    NSDictionary *createdDictionary;
    if(recipe.img64 != nil)
    {
   createdDictionary = @{@"imageencoded": recipe.img64,@"ingrediants":recipe.ingrediants,
                               @"method":recipe.method,@"password":recipe.password,@"title":recipe.title,@"type":recipe.type};
   }
    else{
        createdDictionary = @{@"imageencoded": @"no image",@"ingrediants":recipe.ingrediants,@"method":recipe.method,@"password":recipe.password,@"title":recipe.title,@"type":recipe.type};
    }

    [_table insert:createdDictionary completion:^(NSDictionary *insertedItem, NSError *error) {
        if (error) {
            NSLog(@"Error: %@", error);
        } else {
            NSLog(@"Item inserted, id: %@", [insertedItem objectForKey:@"id"]);
        }
    }];

}

-(void)updateRecipe:(RecipeAzure*)r
{
    NSLog(@"BEFORE AN EXCEPTION");
    NSLog(r.rid);
  NSDictionary *createdDictionary = @{@"id":r.rid,@"imageencoded": r.img64,@"ingrediants":r.ingrediants,
                          @"method":r.method,@"password":r.password,@"title":r.title,@"type":r.type};
    [_table update:createdDictionary completion:^(NSDictionary *item, NSError *error) {
        if(error)
        {
            NSLog(@"%@",error);
        }
    }];
}




@end

CustomCell 类实际上没有任何逻辑。所以这里是 .h 文件!

#import <UIKit/UIKit.h>

@interface CustomCell : UITableViewCell
@property (weak, nonatomic) IBOutlet UILabel *RecipeTitle;
@property (weak, nonatomic) IBOutlet UILabel *IngrediantsLabel;
@property (weak, nonatomic) IBOutlet UIImageView *RecipeImage;

@end

【问题讨论】:

  • 空方法、名称如c 的变量以及像RecipeAzureCustomCell 这样的整个类甚至都没有包含在内存泄漏问题中?祝你好运。
  • 是的,很抱歉格式化!我必须承认,它需要做一些事情!我现在要上传 RecipeAzure 和 CustomCell 类!
  • 你为什么到处使用_foo?您几乎不应该使用_foo 来访问变量。请改用self.foo
  • _foo vs. self.foo 是风格。除非属性设置为copy,否则它在这里几乎没有影响。
  • @Almo 真正的问题是我偶尔会在从 CreateRecipeViewController 转换到 BreakfastViewController 时发现此内存泄漏。它不会一直发生,只是偶尔会奇怪!

标签: ios objective-c memory-leaks


【解决方案1】:

不确定你在这里做什么:

// A wrapped response block that decrements the busy counter
MSFilterResponseBlock wrappedResponse = ^(NSHTTPURLResponse *innerResponse, NSData *data, NSError *error)
{
    [self busy:NO];
    NSLog(@"OK");

    response(innerResponse, data, error);
};

但是使用weakSelf模式:

块前:__weak AzureInteraction* weakSelf = self;

然后在区块中:[weakSelf busy:NO]

调用self 的块将保留自身,除非引用显式弱。

此外,您在块中使用_delegate 也同样可怕。尽管该块可能比这个块更同步地完成。

由于堆栈跟踪,我正在仔细检查您的块,但它可能是另一个您没有在此处显示的块,因为它与像素再现有关。无论如何,您需要注意,您正在调用的这些块将保留指向其中对象的指针。

【讨论】:

  • 嘿,谢谢你给它看看!我注释掉了所有的 azureinteraction 代码,泄漏仍然发生!但我一定会听取你对积木的建议! setsharedblocks 是否与创建配方类中的 uiimage 相关?谢谢!
  • 我不知道如何,但这并不意味着它不是。堆栈还提到了一个字符串copy,所以我会在你的块中寻找它。不过,如果您将块中制作的副本分配给 iVar,我不清楚以后如何不一定会发布它。
  • 最明显的答案是父母可能不会被释放。我们在特定回溯的屏幕截图中看到了泄漏,但是(除非我误读了屏幕截图?)这并不意味着没有其他泄漏。首先追逐自定义类/容器,之后只追逐字符串。 :)
【解决方案2】:

我看不出问题,但我建议做一堆代码清理。你有很多像这样奇怪的事情:

NSPredicate *bpred;
if(pred == nil)
{
  bpred = [NSPredicate predicateWithFormat:@"type == 'lunch'"];
}
else{
  bpred = [NSPredicate predicateWithFormat:@"type == 'lunch' AND title == %@",pred];
}

它怎么可能不为零?

还有这个,什么都不做:

self.ai.busyUpdate = ^(BOOL busy)
{
    if (busy)
    {

    } else
    {

    }
};

我会致力于版本控制(您使用的是版本控制对吗?)并删除/修复所有这些内容。然后看看泄漏是否仍然存在。

如果泄漏仍然存在,也许开始删除大块代码只是为了找出泄漏的位置。然后,一旦您缩小了问题范围,您应该能够找到问题并从版本控制中恢复所有已删除的代码。

这极不可能是 iOS 中的错误。 NSString 类是在 1980 年代初创建的,此后没有太大变化,很久以前就修复了任何错误。

【讨论】:

  • 感谢您花时间浏览一下!欣赏它!是的,我现在将开始大量重构代码!我已经注释掉了 CreateRecipe 类中的所有代码,并且从那以后就没有看到内存泄漏!所以我希望开始缩小问题的范围!好吧好吧!我原以为这是我的某种错误,但我发现泄漏工具没有指出我的任何代码很奇怪!无论如何感谢您的建议:)
  • @bdavies6086 由于 Obj-C 工作原理的技术细节,很难以编程方式确定泄漏发生的位置。它可以找到某些类型的泄漏,但不是全部。顺便试一下“分析”功能吗?它可以发现一些漏洞 - 但也会误报。
  • 对!我没有没有!我得调查一下!我刚刚取消了 CreateRecipe 类中字符串被传递回代表早餐视图控制器的区域的注释,并且弹出了内存泄漏!所以我得稍微研究一下!
猜你喜欢
  • 2011-10-13
  • 2011-10-31
  • 1970-01-01
  • 1970-01-01
  • 2012-08-10
  • 2012-10-29
  • 1970-01-01
  • 1970-01-01
  • 2011-07-31
相关资源
最近更新 更多