Accessing superclass properties from subclass - objective-c

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)

Related

Why is it ok instantiatie self from a super init method?

I was doing some coding where I had a class MyClass which inherits from class MySuperClass. MyClass has a property myProperty.
So I was creating an instance of this class from JSON and in a moment of thoughtlessness I wrote my method like this:
+ (instancetype)newFromJSON:(NSDictionary *)json {
MyClass *myObject = [super newFromJSON:json];
myObject.myProperty = someValue;
return myObject;
}
Note that MySuperClass does have a method + (instancetype)newFromJSON:(NSDictionary *)json.
Now, this obviously doesn't work since the call to super newFromJSON will return an instance of MySuperClass which would become the actual type of myObject. This will of course give me a runtime error since MySuperClass doesn't have a myProperty property.
But this got me thinking about something. Why are we able to do seemingly the same thing when we are instantiating objects with a call to [super init]?
Why is it ok to do this:
- (instancetype)init {
self = [super init];
if (self) {
self.myProperty = someValue;
}
return self;
}
Is it because init methods are treated specially in this regard like they are in so many other? Or is it perhaps that assigning to selfchanges the actual type in a way that does not happen when assigning to a regular variable?
The super keyword only indicates from where in the inheritance chain to start looking to find the selector (method) you are invoking. It says to start looking at the current instance's superclass, instead of the instance's class.
What it does not do is change the class type of the self parameter implicitly passed to a method.
Thus, when invoking [super init], the init implementation in the superclass still receives a reference to MySubClass (or whatever).
Note: you can find documentation which states that init may return a different class than the one on which it was invoked. This is common for class clusters. This is because the idiomatic implementation of init simply returns self without constructing a new instance, but it's allowed to.
A few points of clarification:
+ (instancetype)newFromJSON:(NSDictionary *)json {
MyClass *myObject = [super newFromJSON:json];
myObject.myProperty = someValue;
return myObject;
}
When you invoke [super newFromJSON:json], all you are doing is telling the Objective-C runtime to start the search for the method newFromJSON: from self's superclass.
It is not changing the class self.
So, yes, that code is correct and will work fine.
Furthermore, there is absolutely nothing special about the init method and its treatment of super.
There is a bit of a difference in when you are doing + (instancetype)newFromJSON:(NSDictionary *)json versus init. The former is doing both an allocation of memory and initialization of the new instance. init is solely doing the initialization of the instance.
init is special during compilation, in that it does expect you to call [super init] (it will warn you). But effectively it is saying "use my superclass to initialize me first".
Note to do what you want is possible. You just need to have the superclass modify how it allocates memory. You need to do something like:
Parent *myObject = [[[super class] alloc] init];
Here is a code example to hopefully illustrate these points.
Let's say you have these classes:
#interface Parent : NSObject
#property (nonatomic, assign) NSInteger someValue;
+ (instancetype)newInstance;
- (instancetype)init;
#end
#implementation Parent
+ (instancetype)newInstance {
Parent *myObject = [[[super class] alloc] init];
NSLog(#"Creating new item of class %#", NSStringFromClass([myObject class]));
return myObject;
}
- (instancetype)init {
// This [super init] calls NSObject's init
self = [super init];
if (self) {
_someValue = 1000;
}
return self;
}
#end
#interface ClassA : Parent
#property (nonatomic, assign) NSInteger otherValue;
#end
#implementation ClassA
+ (instancetype)newInstance {
ClassA *myObject = [super newInstance];
myObject.otherValue = 2000;
return myObject;
}
- (instancetype)init {
// This [super init] calls ClassA's init
self = [super init];
if (self) {
}
return self;
}
#end
#interface ClassB : Parent
#end
#implementation ClassB
// Default init will be Parent's
#end
#interface ClassC : Parent
#end
#implementation ClassC
- (instancetype)init {
// We are not calling [super init];
// NOTE: This will yield a warning since we are not calling super
return self;
}
#end
If you execute:
ClassA *classA = [[ClassA alloc] init];
ClassB *classB = [[ClassB alloc] init];
ClassC *classC = [[ClassC alloc] init];
Parent *newInstanceParent = [Parent newInstance];
ClassA *newInstanceClassA = [ClassA newInstance];
NSLog(#"classA.someValue = %ld, classB.someValue = %ld, classC.someValue = %ld", classA.someValue, classB.someValue, classC.someValue);
NSLog(#"classA.otherValue = %ld, newInstanceClassA.otherValue = %ld", classA.otherValue, newInstanceClassA.otherValue);
NSLog(#"newInstanceParent is %#, newInstanceClassA is %#", NSStringFromClass([newInstanceParent class]), NSStringFromClass([newInstanceClassA class]));
You'll get output of:
Creating new item of class Parent
Creating new item of class ClassA
classA.someValue = 1000, classB.someValue = 1000, classC.someValue = 0
classA.otherValue = 0, newInstanceClassA.otherValue = 2000
newInstanceParent is Parent, newInstanceClassA is ClassA

Custom getter not called in Objective C

I'm trying to create a getter for a property - for the time being I'm just using the method to build up an NSMutableObject from static arrays but eventually these will be dynamic config settings. In a previous app and from getter and setters not working objective c I did this:
#import "ViewController.h"
#interface ViewController ()
#property (nonatomic) NSMutableDictionary *questions;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[[self questions] setValue:#"FOO" forKey:#"bar"];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
+ (NSMutableDictionary *)questions
{
static NSMutableDictionary *_questions;
if (_questions== nil)
{
NSArray *genders = #[#"male", #"female"];
NSArray *ages = #[#"<10", #">60", #"Other"];
_questions = [[NSMutableDictionary alloc] init];
[_questions setValue:genders forKey:#"gender"];
[_questions setValue:ages forKey:#"age"];
}
return _questions;
}
When I get to the line in viewDidLoad where I try to use the 'questions' property, it doesn't use the custom getter (it just assigns bar:FOO to a nil dictionary). What am I missing?
Try changing the
+ (NSMutableDictionary *)questions
To
- (NSMutableDictionary *)questions
You may also want to change the return type to NSDictionary * to prevent the static variable from being mutated, or return _questions.copy or _questions.mutableCopy
The reason your custom questions method is not being called through setValue:forKey: is that it is a class method:
+ (NSMutableDictionary *)questions is a method defined on the ViewController class, while the property accessor (which setValue:forKey: is looking for) is defined on the instance.
To access your custom method as it is currently defined call the class method:
[[[self class] questions] setValue:#"FOO" forKey:#"bar"];
This may not have the effect you intend, as this value will be shared across all instances of the class.

(iOS) NSMutableArray as property addObject does nothing

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.

Initializing objects using Objective-C, clarification needed

if header file declares
#interface SomeClass: NSObject {
Data* d;
}
#property (nonatomic, retain) Data* d;
Why is the following line in the implementation file giving me a warning (and init method does not get called?)
[[[self d] alloc] init];
The warning i get is
Instance method '-alloc' not found (return type defaults to 'id')
Meanwhile, Data has
- (id) init method, that is not being called.
Please help me understand why.
alloc should be invoked on a class, not on an instance.
interface SomeClass : NSObject
{
Data *d;
}
Declare an init method on SomeClass and make it look like:
- (id) init
{
self = [super init];
if (self)
{
d = [[Data alloc] init];
}
return self;
}
- (void) dealloc
{
[d release];
[super dealloc];
}
Now you do:
SomeClass *c = [[SomeClass init] alloc];
And you can use the class. Note that you should probably read a little more on classes and objects and about memory management too (when you should release c, etc.).
If, by any chance, you have the possibility to use ARC (automatic reference counting), you won't need to take care of releasing stuff. But that doesn't come with Xcode 4.1, only with 4.2 which is not publicly accessible, apparently.
The problem isn't -(id)init, it's -(id)alloc. alloc is a class method of NSObject, which means you send it to the class itself and not to an instance of that class, i.e.:
[Data alloc]; // Correct
[someDataInstance alloc]; // Method not found
When you call [self d], you're given an instance of a Data, which you're then sending a -(id)alloc message to. Since NSObject doesn't have a -(id)alloc (only a +(id)alloc), you get the warning.
You should be doing
self.d = [[Data alloc] init];
As Matt says, alloc is a class method, and must be called on the class itself.

In Objective-C, what happens to the original object when the init method sets self?

I have three Objective-C classes:
#interface ClassA : NSObject{
IBOutlet id<ClassAProtocol>delegate
//other instance variables
}
//methods
#end
#interface ClassB : ClassA{
//instance variables
}
//methods
#end
#interface ClassC : ClassA{
//instance variables
}
//methods
#end
My objective is so that when an instance of ClassA is called for in either code or InterfaceBuilder, an instance of ClassB or ClassC is actually created. Whether it will be ClassB or ClassC depends on a return value of a method in ClassAProtocol as implemented by the delegate object.
#implementation ClassA
static BOOL _initFromSubclass = NO;
-(id)init{
if(_initFromSubclass){
_initFromSubclass = NO;
self = [super init];
}else {
_initFromSubclass = YES;
if([delegate shouldInitClassB]){
self = [[ClassB alloc] init];
}else{
self = [[ClassC alloc] init];
}
}
return self;
}
//other methods
#end
This doesn't work the way I wanted because at the init call, the delegate (set in Interface Builder) is still nil, and so the object created is always ClassC. Also, a ClassA object is created first, then, in its init call, creates a new ClassC object, with a different memory address, and no ClassA object is dealloced. I have three questions:
1)What happens to the original ClassA object? (I think it's leaked, but I want to know).
2)How do I avoid the leak?
3)How do I accomplish what I actually want? By the time the delegate is set (say, in awakeFromNib method), it's too late to reset the object.
Yes I think it will be leaked because it has a retain count of +1 after the alloc but nothing will release it.
You can use [self release] before reassigning the value of self. Some argue that it should be [self dealloc] directly, but I personally prefer the former.
Objects instantiated from nibs are sent initWithCoder: messages I think, so override that instead of init. Although, at this point, I'm still not sure whether delegate will be set.