New to iOS and trying my best to get to grips with memory management and Instruments.
My problem is I'm getting weird memory losses when adding/removing a pickerview. I've set up a very basic example consisting of two buttons: one creates and shows the picker. The other one removes it from the main view.
Analyzer comes up clean and Instruments doesn't report any obvious leaks. BUT - heap memory continues to grow when I repeat this "show and hide" operation.
Here's the code. I'm on XCode 4.5, ARC enabled:
My ViewController.h:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIPickerViewDataSource,UIPickerViewDelegate>
#property (nonatomic,strong) UIPickerView *myPickerView;
- (IBAction)pickerButtonPressed:(id)sender;
- (IBAction)freeButtonPressed:(id)sender;
#end
ViewController.m:
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)pickerButtonPressed:(id)sender {
NSLog(#"Tap");
// create picker view on first button tap
UIPickerView* newPicker = [[UIPickerView alloc] initWithFrame:CGRectMake(20, 20,160, 160)];
newPicker.dataSource = self;
newPicker.delegate = self;
newPicker.showsSelectionIndicator = YES;
self.myPickerView = newPicker;
[self.view addSubview:newPicker];
}
- (IBAction)freeButtonPressed:(id)sender {
NSLog(#"Free");
// remove from view and release
[self.myPickerView removeFromSuperview];
self.myPickerView = nil;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
return 5;
}
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
return 2;
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return #"Meh";
}
#end
Objects List on Instruments shows UIPickerView come and go nicely with every iteration, so it looks like it's being deallocated correctly. Well, not quite. This is a sample from the heapshot:
(sorry I'm not allowed to post images yet)
Now, if I'm actually releasing the pickerview correctly (removing from view and setting its reference to nil) where do those allocations come from?
On a side note - that UIPickerTableViewTitledCell thing on the stack trace strikes me as suspect because some people have had similar problems with leaking pickerviews involving that (see this one and this one), but I can't make head or tails of what I'm supposed to do with this. Not sure if they're actually related to my issue but with any luck they might point in the right direction.
Any tips?
Related
I am a little new to iOS development, coming from a Java / Android background. My understanding is that your custom Protocols or Delegates are like Interfaces in Java land.
If that is the case then I believe these Protocols are also Objects as well.
Case:
Assume 2 ViewControllers, Home and Profile.
1 Presenter, let's call it StuffPresenter gets instantiated individually in both ViewControllers.
StuffPresenter has an initialization method called initWithInteractor that takes in a parameter of Interactor which is a protocol.
Both Home and Profile implement a Protocol called Interactor, which has a method called initStuffInTableView(NSMutableArray *)stuff.
So I have a dilemma where if I am in Home and StuffPresenter relays information then I switch over to Profile, StuffPresenter loads stuff in Home as well as Profile.
Why is this the case?
Here is the code I have setup:
Protocol
#protocol Interactor <NSObject>
- (void)initStuffInTableView:(NSMutableArray *)stuff;
#end
Presenter
#interface Presenter : NSobject
- (id)initWithInteractor:(id<Interactor>)interactor;
- (void)loadStuff;
#end
#implementation {
#private
id<Interactor> _interactor;
}
- (id)initWithInteractor:(id<Interactor>)interactor {
_interactor = interactor;
return self;
}
- (void)loadStuff {
// Load stuff
NSMutableArray *stuff = // Init stuff in array...
[_interactor initStuffInTableView:stuff];
}
#end
Home
#interface HomeViewController : UITableViewController <Interactor>
- (void)initPresenter;
#end
#implementation {
#private
StuffPresenter *_stuffPresenter;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self initPresenter];
[self initPullToRefresh];
}
# pragma mark - init
- (void)initPresenter {
_stuffPresenter = [[StuffPresenter alloc] initWithInteractor:self];
}
- (void)initPullToRefresh {
// Init pull to refresh
// ...
[self.refreshControl addTarget:self
action:#selector(reloadStuff)
forControlEvents:UIControlEventValueChanged];
}
# pragma mark - Interactor
- (void)initStuffInTableView:(NSMutableArray *)stuff {
// Do some work
[self.tableView reloadData];
}
# pragma mark - reloadStuff
- (void)reloadStuff {
[_stuffPresenter loadStuff];
}
# pragma mark - TableView methods here
// TableView methods...
#end
Profile
#interface ProfileViewController : UITableViewController <Interactor>
- (void)initPresenter;
#end
#implementation {
#private
StuffPresenter *_stuffPresenter;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self initPresenter];
}
# pragma mark - init
- (void)initPresenter {
_stuffPresenter = [[StuffPresenter alloc] initWithInteractor:self];
[_stuffPresenter loadStuff];
}
# pragma mark - Interactor
- (void)initStuffInTableView:(NSMutableArray *)stuff {
// Do some work
[self.tableView reloadData];
}
# pragma mark - TableView methods here
// TableView methods...
#end
Problem:
When I go to Profile the app crashes, because initStuffInTableView is being called in Home. Why is this the case?
A protocol is an Objective-C language feature for specifying that a Class (or another protocol) has certain features, for the benefit of the compiler/ARC/the programmer.
A delegate, or delegation, is a design pattern which makes Model View Controller easier. To make the object doing the delegation be more flexible, generally its delegate adopts a protocol.
There are a number of issues in your code:
Your Presenter has a reference cycle with its interactors
You need to call some init method that eventually calls [super init] in your Presenter's initWithInteractor: method.
As others have pointed out, your methods which begin with init violate Objective-C conventions.
It's hard to tell from what you've posted exactly what your problem is, but I'm very suspicious of how it's structured.
You have a single class (Presenter), which you make two instances of, and pass no parameters other than the Interactor.
How could each instance know to load different "stuff" based on which View controller it received as a parameter?
I still want to understand thoroughly what is going on here, but I did solve the problem by doing the following.
In both Home and Profile, I init the StuffPresenter here:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animation];
[self initPresenter];
}
And when exiting a controllerview I do the following:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
_stuffPresenter = nil;
}
I am testing a simple UIPickerView app so that I can use it as a class in my project...
the code works beautifully with ARC but as soon as I turn ARC off the build succeeds but crashes when I click on the display of the picker...the error displayed is "EXC_BAD_ACCESS(code=2, address=oxc)" at the line
return [pickerData1 objectAtIndex:row];
will be extremely grateful if someone can help...I have spent more than 7 working days [60hours+] trying all sorts of options given on the stackoverflow website but just can't get around the issue...needless to say I am trying to learn...
...i have learnt this much that there is a problem with the array...it is being released whereas it should be retained till the 'objectAtIndex' call...but I have tried countless ways of going about this none worked...its because i don't have an in-depth understanding of xcode as yet...trying to learn by doing...usually i am successful in figuring out how to apply sample code in my project and have successfully completed many complex parts of my project (it is an iPad app that deploys augmented reality technology), but am desperately stuck at this rather simple component...
this is the header file:
#import <UIKit/UIKit.h>
#interface PickerText : UIViewController<UIPickerViewDataSource, UIPickerViewDelegate>
{
NSArray *pickerData1;
}
#property (retain, nonatomic) IBOutlet UIPickerView *picker1;
#property (retain, nonatomic) IBOutlet UIButton *buttonForSelection1;
#end
and this is the implementation file
#import "PickerText.h"
#interface PickerText ()
//set tags to enable future incorporation of multiple pickers
#define kPICKER1COLUMN 1
#define kPICKER1TAG 21
#define kButton1Tag 31
#end
#implementation PickerText
#synthesize picker1;
#synthesize buttonForSelection1;
- (void)viewDidLoad
{
[super viewDidLoad];
// assign data in array
pickerData1 = #[#"[tap to select]", #"Item 1", #"Item 2", #"Item 3", #"Item 4", #"Item 5", #"Item 6"];
// Connect data
picker1.tag = kPICKER1TAG;
self.picker1.dataSource = self;
self.picker1.delegate = self;
picker1.hidden = YES;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
// set number of columns of data in picker view
- (int)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
if (pickerView.tag == kPICKER1TAG)
return kPICKER1COLUMN;
else { return 0; }
}
// The number of rows of data
- (int)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
if (pickerView.tag == kPICKER1TAG)
return [pickerData1 count];
else { return 0; }
}
// The data to return for the row and component (column) that's being passed in (ie set the data in picker view)...
//method using NSString
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
if (pickerView.tag == kPICKER1TAG)
return [pickerData1 objectAtIndex:row];
else { return 0; }
}
// Capture the picker view selection
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
if (pickerView.tag == kPICKER1TAG)
{
NSString *selection1 = [pickerData1 objectAtIndex:[picker1 selectedRowInComponent:0]];
//check selection
NSLog(#"%#", selection1);
//change display text on button
[buttonForSelection1 setTitle:selection1 forState:UIControlStateNormal];
[buttonForSelection1 setTitle:selection1 forState:UIControlStateSelected];
}
//to hide picker view after selection
pickerView.hidden=YES;
}
- (IBAction)showPicker:(id)sender
{
if ([sender tag] == kButton1Tag)
{
picker1.hidden = NO;
}
}
#end
many thanks in advance!!
And in case if you want to know what the issue is with ARC turned off. The Problem is here:(1)
#interface PickerText : UIViewController<UIPickerViewDataSource, UIPickerViewDelegate>
{
NSArray *pickerData1;
}
and here(2) -
pickerData1 = #[#"[tap to select]", #"Item 1", #"Item 2", #"Item 3", #"Item 4", #"Item 5", #"Item 6"];
You see, in ARC the default association is strong so when you write the second line, THE pickerData1 holds a strong reference to the autorelease array and it all works properly. But without ARC the default association is assign hence , since in line 2 you have assigned an autorelease array (which is getting released at some later point of time), you are getting the crash. You can learn the difference between assign and strong in the Apple documents.
Better you change the array to a strong property. Though without ARC, strong will simply mean retain.
Cheers, have fun.
Don't bother turning ARC off. You have the option to enable/disable ARC on a file-by-file basis:
-fobjc-arc to turn ARC on for a file in a project that doesn't work with ARC
-fno-objc-arc vice-versa
have been looking around, but I have not found an answer that helped me out. I am wondering how I can keep the scroll position in a scrollview when I move forwards and backwards in a navigation controller. Currently, when I go to another view in the navigation controller, the scroll view resets and is at the top when I go back. Any code would be greatly appreciated. Sorry if my explanation was not good enough. So far this is what I have, I know that this won't work, but what should I do?
- (void)viewWillAppear:(BOOL)animated {
scrollView.contentOffset.y = scrollSave;
[super viewWillAppear:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
CGFloat scrollSave = scrollView.contentOffset.y;
[super viewWillDisappear:animated];
}
The code you have above shouldn't even compile. You could save this scroll position in another variable, but generally the scroll position shouldn't be resetting itself anyway. You should remove any code that is trying to manipulate the content offset at all, and see if it restores it to the correct scroll position upon returning to the view.
With your current approach, scrollSave is a local variable declared in viewWillDisappear so you cannot access the value in viewWillAppear. You will need to create an instance variable or a property. (If the view controller is still in memory, it should maintain the contentOffset so this might not be necessary)
#interface MyViewController ()
#property (nonatomic) CGPoint scrollSave;
#end
#implementation MyViewController
- (void)viewWillAppear:(BOOL)animated {
[scrollView setContentOffset:self.scrollSave];
[super viewWillAppear:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
self.scrollSave = scrollView.contentOffset;
[super viewWillDisappear:animated];
}
Now there is an issue with persistence. If the you navigate back in the navigation controller, this view controller will be released from memory. If you need to save the users position then I would suggest using NSUserDefaults.
#interface MyViewController ()
#property (nonatomic) NSUserDefaults *defaults
#end
#implementation MyViewController
- (void)viewWillAppear:(BOOL)animated {
CGPoint scrollSave = CGPointFromString([_defaults objectForKey:#"scrollSaveKey"]);
scrollView.contentOffset = scrollSave;
[super viewWillAppear:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
[_defaults setObject:NSStringFromCGPoint(scrollView.contentOffset) forKey:#"scrollSaveKey"];
[_defaults synchronize];
[super viewWillDisappear:animated];
}
Kyle's answer should get you storing the variable okay, but in regards to assigning the contentOffset.y, trying animating the scrollview.
[scrollView scrollRectToVisible:
CGRectMake(scrollView.frame.origin.x, scrollSave.y, scrollView.frame.size.width, scrollView.frame.size.height)
animated:NO];
That code would go into viewWillAppear.
I'm trying to learn iOS from an entirely Android background. I would like to build a UIPickerView Util class that can be reused over and over again as a separate class throughout my app and I'm receiving the EXC_BAD_ACCESS message and I'm not sure why. So I have two questions:
I've not seen anything about separating this as a different class, is this because this is an improper way to handle this problem?
What's wrong with this basic (mostly generated) code that would be giving me the EXC_BAD ACCESS message? I've read that this is related to memory issues. I'm using ARC so why is this an issue?
Here are the beginnings of the class that I'm trying to build.
Header file
#import <UIKit/UIKit.h>
#interface PickerTools : UIViewController<UIPickerViewDelegate>
#property (strong, nonatomic)UIPickerView* myPickerView;
-(UIPickerView*)showPicker;
#end
Implementation file
#import "PickerTools.h"
#implementation PickerTools
#synthesize myPickerView;
- (UIPickerView*)showPicker {
myPickerView = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 200, 320, 200)];
myPickerView.delegate = self;
myPickerView.showsSelectionIndicator = YES;
return myPickerView;
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow: (NSInteger)row inComponent: (NSInteger)component {
// Handle the selection
}
// tell the picker how many rows are available for a given component
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
NSUInteger numRows = 5;
return numRows;
}
// tell the picker how many components it will have
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
// tell the picker the title for a given component
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
NSString *title;
title = [#"" stringByAppendingFormat:#"%d",row];
return title;
}
// tell the picker the width of each row for a given component
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component {
int sectionWidth = 300;
return sectionWidth;
}
#end
Here's how I'm calling it from a method in a UITableViewController class:
PickerTools *picker = [[PickerTools alloc]init];
[[self view]addSubview:[picker showPicker]];
What are you doing with showPicker? showPicker is misleading because it doesn't actually show. It only returns a pickerView with a frame. At some point you need to add it to a view using addSubview or use a UIPopoverController or something.
If you are just creating your view controller class within the scope of a method, then it is getting released as soon as that method is done running. Then all bets are off. The picker is trying to access the delegate (which is the view controller) but the view controller is nowhere to be found because it was released.
You'll have to post some usage code. By itself, this code should work, but it's how you're using it.
Also, you don't have to use a UIViewController just to "control" a simple view. Consider making a custom class and just sublcass NSObject.
EDIT:
After looking at your posted code my suspicions were correct. You need to "retain" your PickerTools instance. Meaning, you need to save the "picker" variable (misleading again) as a strong property on the calling view controller. It gets released right after you're adding the pickerview as a subview. The pickerView is alive because it's being retained by it's superview but the object that holds it (the delegate "picker") is dead. Make sense?
I am currently a new developer, and I came across this error; SIGABRT. I have tried developing many apps, and a lot of them get this error. I am developing a sound matching educational app for little kids where they use one picker view in Xcode to match an animal and their sound. My code is as follows:
ViewController.m
#define componentCount 2
#define animalComponent 0
#define soundComponent 1
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize lastAction;
#synthesize matchResult;
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
NSString *actionMessage;
NSString *matchMessage;
int selectedAnimal;
int selectedSound;
int matchedSound;
if (component==animalComponent) {
actionMessage=[[NSString alloc]
initWithFormat:#"You Selected The Animal Named '%#'!",
[animalNames objectAtIndex:row]];
} else {
actionMessage=[[NSString alloc]
initWithFormat:#"You Selected The Animal Sound '%#'!",
[animalSounds objectAtIndex:row]];
}
selectedAnimal=[pickerView selectedRowInComponent:animalComponent];
selectedSound=[pickerView selectedRowInComponent:soundComponent];
matchedSound=([animalSounds count] - 1) -
[pickerView selectedRowInComponent:soundComponent];
if (selectedAnimal==matchedSound) {
matchMessage=[[NSString alloc] initWithFormat:#"Yes, A '%#' Does Go '%#'!"];
[animalNames objectAtIndex:selectedAnimal],
[animalSounds objectAtIndex:selectedSound];
} else {
matchMessage=[[NSString alloc] initWithFormat:#"No, A '%#' Doesn't Go '%#'!"];
[animalNames objectAtIndex:selectedAnimal],
[animalSounds objectAtIndex:selectedSound];
}
lastAction.text=actionMessage;
matchResult.text=matchMessage;
[matchMessage release];
[actionMessage release];
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
if (component==animalComponent) {
return [animalNames objectAtIndex:row];
} else {
return [animalSounds objectAtIndex:row];
}
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if (component==animalComponent) {
return [animalNames count];
} else {
return [animalSounds count];
}
}
- (void)dealloc {
[animalNames release];
[animalSounds release];
[lastAction release];
[matchResult release];
[super dealloc];
}
- (void)viewDidLoad {
animalNames=[[NSArray alloc]initWithObjects:
#"Cat", #"Dog", #"Snake", #"Cow", #"Horse", #"Pig", #"Duck", #"Sheep", #"Bird",nil];
animalSounds=[[NSArray alloc]initWithObjects:
#"Chirp", #"Baa", #"Quack", #"Oink", #"Nay", #"Moo", #"Hiss", #"Bark", #"Purr",nil];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)viewDidUnload{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIPickerViewDataSource, UIPickerViewDelegate> {
NSArray *animalNames;
NSArray *animalSounds;
IBOutlet UILabel *lastAction;
IBOutlet UILabel *matchResult;
}
#property (nonatomic, retain) UILabel *lastAction;
#property (nonatomic, retain) UILabel *matchResult;
#end
Where The SIGABRT Is
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
//at end of row of code above this, there was the error message: signal SIGABRT
}
}
Anyways, I absolutely need help on what I should do in order to get rid of this SIGABRT error. Thank you.
You have to identify the line of code that is causing the problem, something we cannot identify from the snippet of code.
You may want to enable exception breakpoints, as they often can identify the precise exact line of code that is causing the exception. When I encounter exceptions in my development, I'll simply add an exception breakpoint on "All" exceptions (see Breakpoint Navigator Help or the screen snapshot below). That way, if I'm running the program through my debugger, when it encounters an exception, it will stop the code at the offending line, greatly simplifying the process of identifying the source of the problem. It doesn't always work perfectly, but it frequently finds the source of the exception more quickly than other techniques.
For a broader discussion on debugging apps (e.g. using the debugger to single step through your code, see the Xcode User Guide: Debug your App. If you know the problem is in this method, you might want to step through your code, line by line, and the problem will become self-evident.
I'd also suggest you check out My App Crashed, Now What on the Ray Wenderlich site.
You seem to have some formatting issues with your match message:
matchMessage=[[NSString alloc] initWithFormat:#"Yes, A '%#' Does Go '%#'!"];
[animalNames objectAtIndex:selectedAnimal],
[animalSounds objectAtIndex:selectedSound];
I'm surprised the compiler will actually compile this, but anyhow - you need to include the selected strings within the method call stringWithFormat:
Try below:
matchMessage = [[NSString alloc] initWithFormat:#"Yes, A '%#' Does Go '%#'!",
[animalNames objectAtIndex:selectedAnimal],
[animalSounds objectAtIndex:selectedSound]
];
Your compiler will definitely be giving you a warning about this. So your first step should always be to fix the compiler warnings. It's good practice to turn on the 'Treat warnings as errors' option in build settings. This means that the compiler is very strict about what it will accept and run - which means that you have to fix the warnings. There's really no excuse for having warnings from your compiler.
Read the english before the first call stack. What does it tell you? The most common form of sigabrt is where an IBOutlet gets deleted in the .h file and the outlet connection is still there in the storyboard. To solve this, delete all outlet connections in the storyboard, rewrite the IBOutlets, and link them over again.