2014-04-21 5 views
-2

Привет, У меня возникли проблемы с выяснением причины утечки памяти, и я начинаю удивляться, может быть, это какая-то ошибка IOS? Утечка появляется почти случайным образом, и трассировка стека для утечки в инструменте отладчика не показывает никаких методов, которые я физически закодировал? Снимок экрана снижается! Благодаря!IOS NSString Memory Leak

Instruments screenshot

Вот первый возможный виновник!

#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 
+0

Пустые методы, переменные с именами типа 'c' и целые классы, такие как' RecipeAzure' и 'CustomCell', которые даже не включены в вопрос об утечке памяти? Удачи. – nhgrif

+0

Да, извините за форматирование! Должен признаться, нужно немного потрудиться! Теперь я загружу классы RecipeAzure и CustomCell! – bdavies6086

+1

Почему вы используете '_foo' повсюду? Вы почти никогда не должны использовать '_foo' для доступа к переменной. Используйте 'self.foo' вместо –

ответ

0

Я не вижу проблемы, но предлагаю сделать кучу очистки кода. У вас много чего странного:

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-х годов и не сильно изменился с тех пор, все ошибки были исправлены давным-давно.

+0

Спасибо, что нашли время, чтобы просмотреть его! Ценить это! Да, я начну рефакторинг кода много сейчас! Я прокомментировал весь код в классе CreateRecipe и не видел утечки памяти с тех пор! Поэтому, надеюсь, начнем сужать проблему! Хорошо, хорошо! Я ожидал, что это будет какая-то ошибка, но мне было странно, что инструмент утечки не указывал на мой код! Спасибо за совет в любом случае :) – bdavies6086

+0

@ bdavies6086 из-за технических подробностей о том, как работает Obj-C, очень сложно программно определить, где происходит утечка.Он может найти некоторые типы утечек, но не все. Кстати, вы пробовали функцию «Анализ»? Он может найти некоторые утечки, но также будет ложным. –

+0

Правильно! У меня нет! Мне придется задуматься об этом! Я только что раскомментировал область в классе CreateRecipe, где строки переданы обратно делегату, который является контроллером наблюдения и обнаружил утечку памяти! Поэтому мне придется немного заглянуть в это! – bdavies6086

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 в блоке одинаково страшно. Хотя этот блок, вероятно, завершается более синхронно, чем этот.

Я изучаю ваши блоки из-за трассировки стека, но это может быть другое, что вы здесь не показываете, учитывая, что это что-то о пиксельной передаче. Независимо от того, вы должны знать, что эти блоки, которые вы вызываете, будут содержать указатели на объекты внутри них.

+0

Эй, спасибо, что посмотрели! Я прокомментировал весь код azureinteraction и утечка все еще происходит! Но я обязательно буду принимать ваши советы по блокам! Могут ли setsharedblocks быть связаны с uiimage в классе создания рецепта? Благодаря! – bdavies6086

+0

Я не вижу, как, но это не значит, что это не так. Стек также упоминает строку * copy *, поэтому я бы поискал это в ваших блоках. Я не понимаю, как копия, сделанная в блоке, обязательно не будет выпущена позже, если вы назначаете ее iVar. – stevesliva

+0

Наиболее очевидным ответом является то, что родительский элемент не может быть освобожден. Мы видим в утечках скриншотов конкретной обратной линии, но (если я не ошибаюсь в скриншоте?), Это не означает, что нет других утечек. Чередовать пользовательские классы/контейнеры FIRST, только последовать за строками. :) –