I have a small confusion. When I print my array in tableView dataSource method numberOfRowsInSection , my App crashes.
This is my code : in .h file
#interface AddColor : UIViewController<UITableViewDataSource,UITableViewDelegate>
{
UITableView *tblView;
NSArray *arrayColors;
}
#property(nonatomic,retain)NSArray *arrayColors;
#end
In .m file
#synthesize arrayColors;
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:NO];
arrayColors = [NSArray arrayWithObjects:#"blackColor", #"darkGrayColor", #"lightGrayColor", #"whiteColor", #"grayColor", #"redColor", #"greenColor", #"blueColor", #"cyanColor", #"yellowColor", #"magentaColor", #"orangeColor", #"purpleColor", #"brownColor", nil];
tblView=[[UITableView alloc]initWithFrame:CGRectMake(0, 0, 320, 460) style:UITableViewStylePlain];
tblView.delegate=self;
tblView.dataSource=self;
[self.view addSubview:tblView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
NSLog(#"%d",[arrayColors count]);//App crashes here.
return [arrayColors count];
}
My app crashes when I print [arrayColors count];
I find the solution for crash, I simply retain the array in viewDidLoad
[arrayColor retain];
and now working fine. but why my app crashes before when I print the [arrayColor count];
+ [NSArray arrayWithObjects:]
creates an autoreleased array - it's unsepcified when it is released, but most likely it's released when the viewDidLoad method returns, so it's an invalid (garbage) pointer that you access in [arrayColor count].
By retaining the array, you get rid of the deallocation error, but now you're leaking memory. The general approach of solving this the right way is to allocate and initialize the array in one of the initializer methods, like
- (id)init
{
if ((self = [super init])) {
arrayColors = [[NSArray alloc] initWithObjects:..., nil];
}
return self;
}
and then purge it in - dealloc in order not to leak memory:
- (void)dealloc
{
[arrayColors release];
[super dealloc];
}
More on the topic on Apple Developer.
Try using [self.arrayColors count] - use the ivar getters and setters to access the retained property
As an aside, I would strongly advise against hardcoding the size of your tableview. At the very least set the autoresizing mask.
You might've linked the DataSource and Delegate to your TableView instead of the File Owner.
if you do this,
[self setArrayColors:[NSArray arrayWithObjects:#"blackColor", #"darkGrayColor", #"lightGrayColor", #"whiteColor", #"grayColor", #"redColor", #"greenColor", #"blueColor", #"cyanColor", #"yellowColor", #"magentaColor", #"orangeColor", #"purpleColor", #"brownColor", nil]];
it should fix the problem without requiring another retain, but make sure you release it in the dealloc.
[arrayColors release];
Related
This question already has answers here:
app crash in the dealloc
(2 answers)
Closed 9 years ago.
I'm facing small problem,
I declare an array in .h file and allocate it in viewDodLoad method. In dealloc method I check if array not equal to nil then array=nil. But it's crashing in iOS 5.1.1. I can't understand reason for this crash.
My code,
#interface SampleApp : UIViewController
{
NSMutableArray *objArray;
}
#end
#implementation SampleApp
- (void)viewDidLoad
{
[super viewDidLoad];
objArray=[[NSMutableArray alloc]init];
}
-(void)dealloc
{
[super dealloc];
if (objArray!=nil)
{
[objArray removeAllObjects];
[objArray release];objArray=nil;
}
}
Add [super dealloc]; at the end of dealloc method and not at beginning. It is recommended by Apple in its documentation for dealloc method.
When not using ARC, your implementation of dealloc must invoke the superclass’s implementation as its last instruction.
Modify your code as below,
-(void)dealloc
{
if (objArray!=nil)
{
[objArray removeAllObjects];
[objArray release];objArray=nil;
}
[super dealloc];
}
Also, you need not call [objArray removeAllObjects] when you are releasing the entire array. When array is released, internally it will call release on all contained objects.
Hope that helps!
[super dealloc] method must be call in end of this method. Because you can not access variables of the superclass anymore because they are released when you call [super dealloc]. It is always safe to call the superclass in the last line.
-(void)dealloc
{
// ----------- your stuff ------------
[super dealloc];
}
With manual memory management, your -dealloc method looks as follows:
-(void)dealloc
{
[objArray release]; // objArray *may* be nil, and this is
// sufficient to release all elements as well.
// call super at the end
[super dealloc];
}
Additionally, you have a potential memory leak in your method -viewDidLoad. If you do it like your example:
- (void)viewDidLoad
{
[super viewDidLoad];
objArray=[[NSMutableArray alloc]init];
}
you may assign objArray a new pointer even if objArray already holds a valid object. The new pointer value will simply override the old, and thus you cannot release the old anymore.
One way is to check whether objArray is not nil and then release it before assigning it a new value:
- (void)viewDidLoad
{
[super viewDidLoad];
if (objArray) {
[objArray release], objArray = nil;
}
objArray = [[NSMutableArray alloc]init];
}
The better approach however is to employ "lazy initializing properties":
First, define an "internal property" for your array (unless you want the array publicly accessible). In your .m file:
// In your implementation file define a private property in a class extension:
#interface SampleApp ()
#property (nonatomic) NSMutableArray* objArray;
#end
#implementation SampleApp
#synthesize objArray = _objArray; // this will create the setter
-(void)dealloc
{
[_objArray release];
[super dealloc];
}
// Lazy init property: (this is the getter)
- (NSMutableArray*) objArray {
if (_objArray == nil) {
_objArray = [[NSMutableArray alloc] init];
}
return _objArray;
}
- (void) viewDidLoad {
[super viewDidLoad];
// When needing the array, simply *and always* access it
// through the property "self.objArray":
NSUInteger count = [self.objArray count];
}
...
Lazy initialization of properties are quite handy. Basically, you won't worry anymore if they are initialized or not - they simply are, when you use the property accessor.
I've been struggling to solve this memory leak for awhile now, so I'm hoping the community can provide some help. Memory Management is still an issue I'm working to understand (and yes I have the memory management guide).
According to the Instruments Leak tool, I'm leaking an NSArray as soon as I navigate backward (back button w/ Nav Controller) off of the pertinent screen. I'm show all the relevant code I can think of, below, and can share more if needed.
I know that I'm alloc/initing an array in the ordered array function. This is because, to the best of my understanding, sortedArrayUsingSelector returns only pointers to the old array, not a true copy, so if I want to keep the array, I need to copy the values.
The problem is then how do I pass this sorted array to a different class while still properly managing my ownership of it? I release it in the dealloc, and I release it if the function is going to assign a new value, etc. But tbh I don't know if I'm doing this correctly.
Like I said, I'm really struggling still to properly understand how to correctly juggle all the memory management stuff, so any help would be much appreciated.
.h file of the relevant model class
#interface InstalledDataTracker : NSObject {
...other code...
NSArray *orderedZonesArray;
...other code...
}
#property (nonatomic, retain) NSArray *orderedZonesArray;
.m file of the relevant model class
#synthesize orderedZonesArray;
...other code...
- (NSArray *)orderedZonesArray {
if (!orderedZonesArray || installedDataChangedSinceLastRead) {
if (orderedZonesArray) {
[orderedZonesArray release];
}
NSArray *unorderedZones = [NSArray arrayWithArray:[self.installedAreas allKeys]];
orderedZonesArray = [[NSArray alloc] initWithArray:[unorderedZones sortedArrayUsingSelector:#selector(localizedCompare:)]];
}
return orderedZonesArray;
}
- (void) dealloc {
...other code...
[orderedZonesArray release], orderedZonesArray = nil;
[super dealloc];
}
.h in View Controller
#import <UIKit/UIKit.h>
#class InstalledDataTracker;
#interface SBVC_LSC01_ZoneSelect : UIViewController <UITableViewDataSource, UITableViewDelegate> {
... other stuff...
InstalledDataTracker *_dataTracker;
}
#property (nonatomic, retain) InstalledDataTracker *dataTracker;
.m init in View Controller
#synthesize dataTracker = _dataTracker;
- (id)initWithPerson:(NSString *)person {
if (self = [super init]) {
...other stuff...
self.dataTracker = [[InstalledDataTracker alloc] init];
}
return self;
}
- (void)dealloc
{
...other stuff...
[self.dataTracker release];
[super dealloc];
}
Leaking Method in View Controller
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
AbbreviationLookup *lookup = [[AbbreviationLookup alloc] init];
NSString *abbreviatedZone = [self.dataTracker.orderedZonesArray objectAtIndex:[indexPath section]];
cell.textLabel.text = [lookup zoneForAbbreviation:abbreviatedZone];
[lookup release];
return cell;
}
Instruments Leak Trace:
0 libSystem.B.dylib calloc
1 libobjc.A.dylib class_createInstance
2 CoreFoundation __CFAllocateObject2
3 CoreFoundation +[__NSArrayI __new::]
4 CoreFoundation -[NSArray initWithArray:range:copyItems:]
5 CoreFoundation -[NSArray initWithArray:]
6 -[InstalledDataTracker orderedZonesArray]
7 -[SBVC_LSC01_ZoneSelect tableView:cellForRowAtIndexPath:]
Things I've tried
orderedZonesArray = [[[NSArray alloc] initWithArray:[unorderedZones sortedArrayUsingSelector:#selector(localizedCompare:)]] autorelease];
return [orderedZonesArray autorelease];
And a bunch of other stuff I can't remember. Many attempts I've made to properly "release" the ownership created by alloc/init result in some sort of crash/bad access in the view controller. This is contributing to my confusion over where to properly release the array...
Detailed replies very welcome. I still have a great deal to learn!
Thanks a bunch. (Also, I've had to change some class and methods names for project security, so if something doesn't seem to match please mention it and I'll recheck for a typo)
Edit:
#Daniel Hicks, when I remove the initWithArray copy of the sorted array, as follows:
orderedZonesArray = [unorderedZones sortedArrayUsingSelector:#selector(localizedCompare:)];
, I get an EXC_BAD_ACCESS crash when the class tries to access the array from within the View Controller didSelectRowAtIndexPath method (likely the next time the array is accessed, I believe). Here's the method. It crashes on the second NSLog line, so I've left that in for good measure:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"indexPath = %#", indexPath);
NSLog(#"self.dataTracker.orderedZonesArray = %#", self.dataTracker.orderedZonesArray);
NSString *abbreviatedZone = [self.dataTracker.orderedZonesArray objectAtIndex:[indexPath section]];
SBVC_LSC02_ZoneSelect *slz2 = [[SBVC_LSC02_ZoneSelect alloc] initWithPerson:self.selectedPerson andZone:abbreviatedZone];
[self.navigationController pushViewController:slz2 animated:YES];
[slz2 release];
}
In your viewController code, you allocate an InstalledDataTracker object, and then pass it to a retain property. This results in a retain count of 2, not 1. Later, when you release the dataTracker object, you only reduce the retain count by one.
The easiest way to fix it would be to remove the self. prefix so that you are not invoking the automatic retain that is performed by the property accessor. In fact, I would recommend not using the dot syntax at all in your init and dealloc methods. There is some debate on the matter, but in general I think it is best to avoid calling your property accessors unless you have a very good reason to do so.
Here is how I would write it:
- (id)initWithPerson:(NSString *)person {
if (self = [super init]) {
...other stuff...
dataTracker = [[InstalledDataTracker alloc] init];
}
return self;
}
- (void)dealloc {
...other stuff...
[dataTracker release];
[super dealloc];
}
This is because, to the best of my understanding, sortedArrayUsingSelector returns only pointers to the old array, not a true copy, so if I want to keep the array, I need to copy the values.
This is a misinterpretation. sortedArrayUsing..., similar other such functions, returns an array which contains the same pointer VALUES as in the original array. And, as the sorted array copy was made, the reference counts of the OBJECTS pointed to were incremented (ie, retain was done on each copied pointer). So the sorted array and the original are both "equals", and neither "owns" the objects more than the other one does. (In fact, examining the innards of the two arrays you'd not be able to tell which was copied from which, other than if you noticed that one is sorted and the other isn't.)
So there's absolutely no need to make the additional copy of the sorted array.
When you make that additional copy, you're using an [[alloc] init...] operation which returns a retained array. You then return that array to your caller without doing autorelease on it, meaning that it will leak if your caller does not explicitly release it, and meaning that the Analyzer will complain about it (since you can only return a retained object from copy... et al).
I'm curious if anyone has ideas for managing multiple ViewControllers from a TableView. I have a list of roughly seven items I am displaying in a TableView with a ViewController dedicated to each. My first thought is to initialize an array with the various ViewControllers.
NSMutableArray *viewControllers = [[NSMutableArray alloc] initWithCapacity:7];
[viewControllers addObject:[[ViewController1 alloc] initWithNibName:#"View1" bundle:nil]];
[viewControllers addObject:[[ViewController2 alloc] initWithNibName:#"View2" bundle:nil]];
...
Then reference that array to load the appropriate view on item selection.
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[self.navigationController pushViewController:[viewControllers objectAtIndex:indexPath.row] animated:YES];
}
I'm really not sure if this is an appropriate approach. Any direction would be great.
EDITED:
Based on the feedback from Ryan and Joe I implemented an object to hold my table items. Abbreviating my problem also caused some confusion on implementation details. Added the full solution to manage both view controllers and selecting tab bar items.
TableNavigationItem.h
#import
#interface TableNavigationItem : NSObject {
NSString *title;
NSNumber *tabIndex;
id viewController;
}
#property (nonatomic, retain) NSString *title;
#property (nonatomic, retain) NSNumber *tabIndex;
#property (nonatomic, retain) id viewController;
#end
TableNavigationItem.m
#import "TableNavigationItem.h"
#implementation TableNavigationItem
#synthesize title;
#synthesize viewController;
- (id) init{
if(self = [super init]){
self.title = #"";
}
return self;
}
- (void) dealloc {
[title release];
[tabIndex release];
[viewController release];
[super dealloc];
}
#end
Then initialize per Joe's suggestion.
NSMutableArray *mutableArray = [[NSMutableArray alloc] initWithCapacity:7];
TableNavigationItem *navItem;
// view 1
navItem = [[TableNavigationItem alloc] init];
navItem.title = #"View 1";
navItem.tabIndex = [NSNumber numberWithInt:1];
[mutableArray addObject:navItem];
[navItem release];
// view 2
navItem = [[TableNavigationItem alloc] init];
navItem.title = #"View 2";
navItem.viewController = [ViewController2 class]];
[mutableArray addObject:navItem];
[navItem release];
...
// store the navigation items
self.tableItems = [NSArray arrayWithArray:mutableArray];
[mutableArray release];
Then
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
TableNavigationItem *navItem = [tableItems objectAtIndex:indexPath.row];
if(navItem.viewController != nil){
[self.navigationController pushViewController:[[[navItem.viewController alloc] init] autorelease] animated:YES];
}
else if(navItem.tabIndex != nil){
[((MyAppDelegate *)[UIApplication sharedApplication].delegate).tabBarController setSelectedIndex:[navItem.tabIndex integerValue]];
}
}
If all the views those controllers manage are visible on the screen immediately, there is nothing wrong with that approach. Make sure you release the array of VC's in -viewDidUnload, and recreate it in -viewDidLoad, so the runtime can unload all those extra objects when the next view is pushed onscreen. And be aware, only the root view controller will receive view lifecycle events; The view controllers you create and manually add the owned views to the table will not get those methods called. You'll have to implement some plumbing to get those view lifecycle events into the 'subviews', through notification or delegation.
The best answer to your question is "Instrument it". Run the Allocations and VM instruments at a minimum, and check to see how much memory those view controllers are consuming. If you want to improve your skillz with Instruments, watch the Performance session from WWDC 2011, they did a great job teaching how to use it to find memory and performance issues.
That sounds fine to me. The only concern I would have is whether your view controllers are RAM-heavy, in which case you may want to make a decision: is it better to preallocate everything (i.e. are you sure you can fit all of those controllers' state within available memory?) or is it better to take the latency hit to load the appropriate view controller as-needed?
It looks like your ViewControllers are of different classes. If that's the case (and if each one always uses the same respective nib), I would consider implementing a custom -init method on each and making your array of choices one of Class objects. That's just a matter of personal preference, though.
One more thing: You will want to autorelease those view controllers or you'll leak memory no matter what.
I have an nsarray that when I NSLog it from one of my methods (inside WorkOutList.m) I can see its contents, but when I try to look inside it from a different method inside WorkOutList.m it seems to be empty. I am aware that my memory management needs some work, could anyone help explain whats happening here?
I am using popViewControllerAnimated:YES to return the view from a tableView back to a view controller, but just before I do that I set my array in a method inside WorkOutList. When I NSLog that array from that same method I am returned results, however when i NSLog it from else where it is returned empty.
I have been told that it might be the viewDidLoad method where the array is init, but that the other array in that method customWorkouts still retains its data. So i dunno, any explanation would be really helpful. I want this to work, but I also really want to understand it so I can get on with coding correctly.
Thanks so much!
WorkOutList.h
#import <UIKit/UIKit.h>
#interface WorkOutList : UIViewController {
NSManagedObjectContext *managedObjectContext;
NSMutableArray *customWorkouts;
NSArray *passedWorkout;
}
#property(nonatomic, retain)NSManagedObjectContext *managedObjectContext;
#property(nonatomic, retain)NSMutableArray *customWorkouts;
#property(nonatomic, retain)NSArray *passedWorkout;
-(IBAction)customWorkouts:(id)sender;
-(void)passWorkoutBack:(NSArray *)workout;
#end
WorkOutList.m
#implementation WorkOutList
#synthesize managedObjectContext, customWorkouts, passedWorkout;
- (void)viewDidLoad {
[self setupContext];
NSLog(#"View Did Load");
customWorkouts = [[NSMutableArray alloc] init];
passedWorkout = [[NSArray alloc] init];
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self fetchWorkoutList];
NSLog(#"View will Appear");
NSLog(#"Array from View Will Appear : %#", passedWorkout);
}
-(IBAction)customWorkouts:(id)sender{
CoCoachAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
SelectedWorkout *selectedWorkout = [[SelectedWorkout alloc] initWithStyle:UITableViewStyleGrouped];
[selectedWorkout recieveNeededData:customWorkouts];
[appDelegate.practiceNavController pushViewController:selectedWorkout animated:YES];
[selectedWorkout release];
}
-(void)passWorkoutBack:(NSArray *)workout{
passedWorkout = workout;
[passedWorkout retain];
}
- (void)dealloc {
[super dealloc];
}
SelectedWorkout.h
#import <UIKit/UIKit.h>
#interface SelectedWorkout : UITableViewController {
NSMutableArray *workoutListForTable;
}
#property(nonatomic,retain)NSMutableArray *workoutListForTable;
-(void)recieveNeededData:(NSMutableArray *)workoutList;
#end
SelectedWorkout.m(aside from all the stuff to set up the tableView)
#implementation SelectedWorkout
#synthesize workoutListForTable;
-(void)recieveNeededData:(NSMutableArray *)workoutList{
if (workoutListForTable != workoutList) {
workoutListForTable = workoutList;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
CoCoachAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
WorkOutList *workoutListView = [[WorkOutList alloc]init];
[workoutListView passWorkoutBack:[workoutListForTable objectAtIndex:indexPath.row]];
[appDelegate.practiceNavController popViewControllerAnimated:YES];
}
- (void)dealloc {
[workoutListForTable release];
[super dealloc];
}
NSLog(#"other table : %#", workoutListForTable);
[workoutListForTable retain];
}
In this line:
passedWorkout = [[NSArray alloc] init];
You're creating an immutable array with nothing in it. What do you want it to contain?
It looks to me that although you are initializing your array with or so I assume, since on the other line you are only allocating an empty array.
[self fetchWorkoutList]
You are resetting it here every time:
[workoutListView passWorkoutBack:[workoutListForTable objectAtIndex:indexPath.row]];
As a note here:
-(void)passWorkoutBack:(NSArray *)workout{
passedWorkout = workout;
[passedWorkout retain];
}
You have the property passedWorkout as retain already, however you need to call it on self
-(void)passWorkoutBack:(NSArray *)workout{
self.passedWorkout = workout;
}
Here is some of the code from one of my classes, I was wondering am I handling the memory right or am I leaking anywhere?
#implementation CardViewController
#synthesize playerImage;
#synthesize cardLabel;
#synthesize card;
#synthesize frontView;
#synthesize backView;
#synthesize nameLabel;
#synthesize infoLabel;
#synthesize delegate;
-(void) initialiseButtons
{
NSLog(#"initialiseButtons %d",totalButtons);
int ypos = playerImage.frame.origin.y + playerImage.frame.size.height + 42;
for(int i=0; i<totalButtons; i++)
{
StatView *sv = [[StatView alloc] initWithYPos:ypos];
sv.tag = 100 + i;
[sv.overlayButton addTarget:self action:#selector(statTapped:)
forControlEvents:UIControlEventTouchUpInside];
sv.overlayButton.tag = 10 + i;
[self.frontView addSubview:sv];
ypos += 26;
}
}
-(IBAction) statTapped:(id) sender
{
UIButton *tappedButton = (UIButton *)sender;
int tag = tappedButton.tag;
if(delegate && [delegate respondsToSelector:#selector(cardTapped:)]) {
[delegate cardTapped:tag-10];
}
}
-(void) viewDidLoad
{
NSLog(#" viewDidLoad CVC");
[self initialiseButtons];
metaData = [[NSArray alloc]
initWithObjects:#"Height",#"Weight",#"Games",#"Attack",#"Defense", nil];
}
-(void) setCard:(Card *)newCard
{
NSLog(#"setCard");
[card release];
card = [newCard retain];
[self updateUI];
}
- (void)dealloc
{
[card autorelease];
[metaData autorelease];
[super dealloc];
}
#end
Where should I release StatView *sv = [[StatView alloc] initWithYPos:ypos];
If i released it every loop wouldnt that cause problems?
Besides that do I handle the rest of the memory ok?
Thanks
-Code
Yes, you should release that StatView at the end of each loop iteration, when you've inserted it into the view hierarchy.
You should try the analyzer built into XCode as it is very good at finding these type of memory leaks. Have a look.
Release the new StatView after this line
[self.frontView addSubview:sv];
[sv release]; // frontView retains sv
Release all properties declared as retain or copy in dealloc. Candidate properties: playerImage, cardLabel etc. Send a release message, not autorelease
\\[card autorelease];
[card release];
In viewDidUnload release all properties that are declared as IBOutlet and set the variable to nil
[frontView release], frontView = nil;
You should release it as soon as you've added it to the view hierarchy (by sending addSubview: with the newly-allocated view as an argument). This is because UIView objects retain their subviews.
One other problem I notice: your setCard method should first check whether the new and old cards are identical, and do nothing in that case. Otherwise, you may release your existing card, then try to retain it again, only to find that it has been dealloced.