I have a subclass of UIViewController with an NSMutableArray as a property to use as the data source for a UITableView. I create an instance of the class in my storyboard.
I want to populate the array with the addObject: method but if I try, the array always returns (null).
I read that #synthesize doesn't init the array and I might need to override -init and init the NSMutableArray there but -init never gets called.
How is this supposed to work?
You need to create an instance of NSMutableArray and assign it to the property.
Since the object with the property is a UIViewController created in a storyboard, you can do it in a few different places. You can override initWithCoder:, or awakeFromNib, or viewDidLoad.
If you override initWithCoder:, it is imperative that you call the super method.
If you do it in viewDidLoad, the array won't be created until the view is loaded, which doesn't have to happen right away.
I recommend doing it in awakeFromNib:
#synthesize myArray = _myArray;
- (void)awakeFromNib {
_myArray = [[NSMutableArray alloc] init];
}
Another option is to just create the array lazily by overriding the getter method of the property:
#synthesize myArray = _myArray;
- (NSMutableArray *)myArray {
if (!_myArray)
_myArray = [[NSMutableArray alloc] init];
return _myArray;
}
If you do this, it is very important that you always access the array using the getter method (self.myArray or [self myArray]) and never by accessing the instance variable (_myArray) directly.
Here's what your code will need:
#interface BlahBlah : UIViewController
#property (...) NSMutableArray *myArray;
#end
At first, *myArray is just a pointer that's equal to nil. You can't use this as an array yet. It needs to be set to a valid NSMutableArray instance. You need to do this in the designated initializer:
#implementation BlahBlah
#synthesize myArray;
// -initWithNibName:bundle: is the designated initializer for UIViewController
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle
{
self = [super initWithNibName:nibName bundle:nibBundle];
if (self) {
myArray = [[NSMutableArray alloc] init];
// now you can add objects to myArray
}
return self;
}
- (void)dealloc
{
[myArray release];
[super dealloc];
}
Note that you can't just override -init; you must override the designated initializer for your class. Figure out what subclass you are implementing, find out what its designated initializer is, override that (but call the superclass implementation as is often necessary), then init your properties.
Here's Apple documentation regarding multiple/designated initializers.
#synthesis neither do alloc. So alloc and init your array. Then it should work.
Related
I'm running into some trouble implementing a simple super/sub class scheme. I declare an NSMutableDictionary in the superclass, and am trying to access it in a subclass, but it only returns null. Any help would be appreciated.
#interface RootModel : NSObject <Updatable, TouchDelegate>
#property (nonatomic) NSMutableDictionary *gameValues;
#end
#interface SubclassModel : RootModel
#end
#implementation RootModel
- (id)initWithController:(id)controller {
if ((self = [super init])) {
_controller = controller;
_gameValues = [NSMutableDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithFloat:300.0f], KEY_JUMP_VELOCITY,
[NSNumber numberWithFloat:49.47f], KEY_GRAVITY,
[NSNumber numberWithFloat:0.25f], KEY_JUMP_TIME_LIMIT,
nil];
NSLog(#"%#", _gameValues);
}
return self;
}
#implementation SubclassModel
- (id)init{
if ((self = [super init])) {
// This NSLog prints "(null)" if using super.gameValues or self.gameValues, why?
NSLog(#"subclass: %#", super.gameValues);
}
return self;
}
#end
What am I doing wrong?
As Catfish_Man answered, your init method needs to call [super initWithController:]. However you seem to show a misunderstanding of the class/super class inheritance model with your comment:
My superclass is initialized by another controller class. Any calls to the super's properties (which were initialized in initWithController:) are valid (they return values, not null).
When you create an instance of your SubclassModel then that instance has as part of itself a RootModel instance. That RootModel instance is not shared with any other instance of SubclassModel or RootModel.
So if "another controller class" creates and initialises and instance of RootModel, which in turn displays your NSLog output, then that is a totally different object to your SubclassModel instance - and the RootModel that is part of your SubclassModel instance is not initialised, as you don't call [super initWithController:], hence you NSLog in SubclassModel shows nil.
Your subclass init method needs to call [super initWithController:], since that's where the actual initialization happens.
(or the superclass initWithController: needs to call [self init], and you need to move the initialization work you're relying on to init)
After many hours wasted, I officially turn to the experts for help!
My problem lies with using a NSMutableArray as an instance variable, and trying to both add objects and return the array in a method in my class. I am obviously doing something fundamentally wrong and would be grateful for help...I have already tried all the suggestions from other similar questions on stackoverflow, read apples documentation, and basically all combinations of trial and error coding I can think of. The mutable array just alway returns (null). I've even tried creating properties for them, but still the array returns (null) and then I also am running into memory management problems due to the retain while setting the property, and the init in the init method for the class.
Here is what I am trying to do:
1) Loop through a series of UISwitches and if they are 'switched on', add a string to the NSMutableArray
2) Assign this mutable array to another array in another method
Any help much appreciated,
Andy
And for some code...
fruitsViewController.h
#import <UIKit/UIKit.h>
#interface fruitsViewController : UIViewController
{
NSMutableArray *fruitsArr;
UISwitch *appleSwitch;
UISwitch *orangeSwitch;
}
#property (nonatomic,retain) NSMutableArray *fruitsArr; // ADDED ON EDIT
#property (nonatomic,retain) IBOutlet UISwitch *appleSwitch;
#property (nonatomic,retain) IBOutlet UISwitch *orangeSwitch;
- (IBAction)submitButtonPressed:(id)sender;
#end
fruitsViewController.m
#import "fruitsViewController.h"
#implementation fruitsViewController
#synthesize fruitsArr; // ADDED ON EDIT
#synthesize appleSwitch, orangeSwitch;
/* COMMENTED OUT ON EDIT
-(id)init
{
if (self = [super init]) {
// Allocate memory and initialize the fruits mutable array
fruitsArr = [[NSMutableArray alloc] init];
}
return self;
}
*/
// VIEW DID LOAD ADDED ON EDIT
- (void)viewDidLoad
{
self.fruitsArr = [[NSMutableArray alloc] init];
}
- (void)viewDidUnload
{
self.fruitsArr = nil;
self.appleSwitch = nil;
self.orangeSwitch = nil;
}
- (void)dealloc
{
[fruitsArr release];
[appleSwitch release];
[orangeSwitch release];
[super dealloc];
}
- (IBAction)submitButtonPressed:(id)sender
{
if ([self.appleSwitch isOn]) {
[self.fruitsArr addObject:#"Apple"; // 'self.' ADDED ON EDIT
}
if ([self.orangeSwitch isOn]) {
[self.fruitsArr addObject:#"Orange"; // 'self.' ADDED ON EDIT
}
NSLog(#"%#",self.fruitsArr); // Why is this returning (null) even if the switches are on?!
[fruitsArr addObject:#"Hello World";
NSLog(#"%#",self.fruitsArr); // Even trying to add an object outside the if statement returns (null)
}
#end
It seems like your init function is never called. If you're initializing this view controller from a NIB, you need to use initWithCoder. If not, just declare your fruitsArr in viewDidLoad.
Use view did load instead of init...
- (void)viewDidLoad
{
fruitsArr = [[NSMutableArray alloc] init];
}
Change that init for viewDidLoad and see what happens
Is your init method ever being called (in complicationsViewController). Add a NSLog to check this, you might be calling initWithNib: maybe.
At viewDidUnload you should remove self.fruitsArr = nil;, or, if you want to keep it, then initialize the fruitsArr in viewDidLoad (and remove it from init).
because fruitsArr don't be init.
you should do this first:
fruitsArr = [[NSMutableArray alloc] init];
so, I think you don't run - (id)init before you use fruitsArr.
This is one segment of codes used in one of my project for managing one of my class instance:
#interface C: NSObject
{
NSMutableArray *myArr;
}
#property (nonatomic,retain) NSMutableArray *myArr;
//...
#end
#implementation C
#synthesize myArr;
//...
-(id)init
{
//...
myArr = [[NSMutableArray alloc] init];
//...
}
//version 1 of dealloc method
-(void)dealloc
{
//...
self.myArr = nil;
//...
}
//version 2 of dealloc method
-(void)dealloc
{
//...
[myArr release];
//...
}
here the version 1 dealloc method doesn't work and Xcode says something like "EXC_BAD_ACCESS..." and the app crashed.
if I modified the dealloc method as version 2, it works.
Does anybody have any idea why?
Thx in advance.
As Duncan said, the EXEC_BAD_ACCESS error means that the object doesn't exist anymore.
This is probably due to the fact that myArr is being released before the dealloc gets called.
To facilitate memory management and to keep track of reference counts, I like to make it clearer in the init methods, for example:
-(id)init
{
//...
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:0];
self.myArr = array;
[array release];
//...
}
By using the generated setter self.myArr = array; you are ensuring that the reference count is being delt with correctly, it will release the old value and retain the new one for you.
#MadhavanRP : if the property is a retained property:
#property(nonatomic, retain) NSMutableArray *myArr;
calling
self.myArr = nil
is exactely the same as calling
[myArr release];
myArr = nil;
Edit: #Sylvain beat me to it :)
This is OK even if it's bad idea/confusing to have same name for iVar and property. I removed the iVar declaration.
#interface C: NSObject
{}
#property (nonatomic,retain) NSMutableArray *myArr;
//...
#end
Generate your iVar using #synthetize.
#implementation C
#synthesize myArr = _myArr;
//...
Your init is all wrong. You are assigning the iVar without using the setter method.
-(id)init
{
//...
// You were doing
// _myArr = [[NSMutableArray alloc] init];
// You should do
NSMutableArray array = [[NSMutableArray alloc] init];
self.myArr = array;
[array release];
// You could avoid the temporary variable but this confuse XCode static analyzer
//...
}
This is OK. I guess that #synthetize generated another iVar to back your property.
This other iVar was not properly assign.
You would not notice this if you do not user self.myArr (or the equivalent [self myArr]).
This confusion is main reason why hardcore ObjC programmer do not like the new property thing. :) This confuse new programmers.
//version 1 of dealloc method
-(void)dealloc
{
//...
self.myArr = nil;
//...
}
This is not good as you bypass the setter, as in init method. This was working anyway.
//version 2 of dealloc method
-(void)dealloc
{
//...
[myArr release];
//...
}
It looks like your myArr object is being messaged when it should not be, in the first case you hide the problem by setting it to nil, in the second you don't. Either method should not crash. When do you call [super dealloc]; (you should call it at the end of the dealloc implementation).
In the iPhone objective-c world, I've seen this pattern everywhere and I use it myself all the time without really understanding what is going on:
In Test.h
#interface Test: UIViewController
{
NSMutableArray *testArray;
}
#property (retain, nonatomic) NSMutableArray *testArray;
And in Test.m
#implementation Test
#synthesize testArray
- (void) viewDidLoad
{
// why do we do this?
NSMutableArray *init = [[NSMutableArray alloc] init];
self.testArray = init;
[init release];
[self.testArray addObject: #"A"]; // why can't I do this directly?
...
}
- (void) dealloc
{
[testArray release];
[super dealloc];
}
My question is: if testArray has a retain on it when it's declared in the property, why do we need to create a new NSMutableArray init object, assign that to testArray and release? Why can't I just start using testArray in viewDidLoad without doing anything else?
I know there's some debate over the best way of doing this (creating a new object, or using an autorelease object), but in both cases, we end up with testArray with a retain count of 1. Which I believe the 'retain' property already gives it. So why the need to create this init object?
The 'retain' property doesn't automatically create an NSMutableArray for you. Rather, it simply indicates that whenever you do assign something to that property, it will be retained.
If your code were this:
- (void) viewDidLoad
{
[self.testArray addObject: #"A"];
}
Then self.testArray would be nil, and thus it would be essentially a no-op. Until you assign something to self.testArray, it's empty.
Here's what's going on.
- (void) viewDidLoad
{
// we need to assign an NSMutableArray to self.testArray.
NSMutableArray *init = [[NSMutableArray alloc] init];
// The array has been retained once (by the call to |alloc|)
self.testArray = init;
// The array is assigned to a property with the 'retain' attribute
// Thus, the array has now been retained twice
[init release];
// We release the array, so it now is retained once.
// We now have an array in self.testArray, so we can add something to it.
[self.testArray addObject: #"A"];
}
The "retain" in the #property directive specifies that the setter should retain the input value instead of simply copying the value. It has nothing to do with allocating (setting aside memory) and initializing (constructing the object) the object. retain on the #property directive simply increments the retain count when the setter is called (which alllows you to do something like self.myobject = something without specifically calling retain.
I am using the iPhone SDK and have an issue doing something simple. I am trying to add an NSNumber object to an NSMutableArray instance variable. I tried adding NSNumber card to NSMutableArray viewedCardsArray, however without breaking, it does not get added to the array. Here is the code.
/////////////////////////////////////////////////////
// Inside the header file Class.h
#interface MyViewController : UIViewController {
NSMutableArray *viewedCardsArray;
//snip ...
}
#property (nonatomic, retain) NSMutableArray *viewedCardsArray;
#end
/////////////////////////////////////////////////////
// Inside the methods file Class.m
#import "StudyViewController.h"
#implementation StudyViewController
#synthesize viewedCardsArray
//snip ...
- (IBAction)doShowCard {
//snip ...
NSNumber *cardIdObject = [[NSNumber alloc] initWithInt:(int)[self.currentCard cardId]];
[viewedCardsArray addObject: cardIdObject];
[cardIdObject release];
}
So this code executes, and does not seem to leak (according to the Leaks performance tool). However when stepping through the code, at no point does CardIdObject appear in viewedCardsArray.
Looking through SO, I know these basic questions are pretty common to ObjC newbies (like me) so apologies in advance!
Have you initialized your viewedCardsArray? If not you need to somewhere - this is usually done in the init method for your class:
- (id)init
{
self = [super init];
if(self) {
viewedCardsArray = [[NSMutableArray alloc] init];
}
return self;
}
Then it is released in the dealloc method:
- (void)dealloc
{
[viewedCardsArray release];
[super dealloc];
}
Perspx has outlined one way of initializing the array. However, you can also use the class methods provided by NSArray:
self. viewedCardsArray = [NSMutableArray array];
This can go in init or elsewhere.
Note: The object will be autoreleased.