[英]IOS NSString Memory Leak
嗨,我在找出內存泄漏的原因時遇到了麻煩,我開始懷疑它是否是某種 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
不確定你在這里做什么:
// 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
塊將保留 self 。
此外,您在塊中使用_delegate
也同樣可怕。 盡管該塊可能比這個塊更同步地完成。
由於堆棧跟蹤,我正在仔細檢查您的塊,但它可能是另一個您沒有在此處顯示的塊,因為它與像素再現有關。 無論如何,您需要知道您正在調用的這些塊將保留指向其中的對象的指針。
我看不到問題,但我建議進行大量代碼清理。 你有很多像這樣奇怪的事情:
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 年代早期創建的,此后沒有太大變化,很久以前就修復了任何錯誤。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.