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
Related
I am trying to subclass SKSpriteNode so that I can use it to create different characters with different properties for a game. I know this should be a trivial task, but I am having a hard time understanding how to initialize a variable when instances are created.
In the header I have:
#import <SpriteKit/SpriteKit.h>
#interface MyClass : SKSpriteNode
#property int myVariable;
#end
And in the implementation:
#import "MyClass.h"
#interface MyClass ()
#end
#implementation MyClass
#synthesize myVariable;
- (id)init {
if (self = [super init]) {
myVariable = 100;
}
return self;
}
When I create the node in the scene:
#implementation GameScene
{
MyClass *mySprite;
}
- (void)didMoveToView: (SKView *) view
{
mySprite = [MyClass spriteNodeWithImageNamed:#"image.png"];
}
The value of myVariable is 0, which I assume it means that spriteNodeWithImageNamed does not execute the init method. If I use:
mySprite = [[MyClass alloc] init]; // or mySprite = [MyClass new];
mySprite = [MyClass spriteNodeWithImageNamed:#"image.png"];
The variable name is correctly set to 100, but then reverted to 0 by spriteNodeWithImageNamed.
I have also tried:
mySprite = [[MyClass alloc] init]; // or mySprite = [MyClass new];
[mySprite setTexture:[SKTexture textureWithImageNamed:#"image.png"]];
In this case the value of myVariable is correct, but no image appears when I add the node to the scene. What init method is called when spriteNodeWithImageNamed is used? Am I improperly overriding the SKSpriteNode init method?
You have done everything correctly with creating a class, but you need to keep in mind that you are calling the factory method spriteNodeWithImageNamed, which then initializes and allocates by itself. Thus, if you want to use your custom init method you need to override the factory method as well.
Inside of your implementation of MyClass, override the method to call your initializer:
+ (instancetype) spriteNodeWithImageNamed:(NSString *)imageName{
MyClass *newClass = [[MyClass alloc] init];
SKTexture *spriteTexture = [SKTexture textureWithImageNamed:imageName];
newClass.size = spriteTexture.size;
newClass.texture = spriteTexture;
return newClass;
}
Hope this helps, let me know if you have questions.
*EDIT: Im actually a little tempted to ditch this, only because SKSpriteNode doesn't have an init method (or at least not that we know of). So unless you want to dive deep into Apple's mysteries then just do it this way:
+ (instancetype) spriteNodeWithImageNamed:(NSString *)imageName{
MyClass *newClass = [[MyClass alloc] initWithImageNamed:imageName];
newClass.myVariable = 100;
return newClass;
}
You could instead override initWithImage to put in myVariable, but the whole point is that I'd avoid trying to use the init initializer with SKSpriteNode.
Here is how I do it. Create your own custom init method, and simply call the default spritenode initWithImage.
-(id)initShipWithType:(ShipType)type {
self = [super initWithImageNamed:[self getShipSprite:type]];
if (self) {
}
return self;
}
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
I'm unsure how I should initialise the various properties in an objective-C class. Please assume I'm a very new user to Objective-C in your answers...
I have the following classes:
Test class
#interface Test : NSObject
#property (nonatomic, strong) NSString *name;
#end
TestManager class
#interface TestManager : NSObject
#property (nonatomic, strong) NSMutableArray *tests; // array of Test objects (array size unknown until runtime)
#end
Controller class
#interface TestController : NSObject
#property (nonatomic, strong) TestManager *aManager;
-(void)initManager;
-(void)doSomething;
#end
I want to have an method like initManager called:
-(void)initManager
{
// how can I init the aManager which will have an array of Test objects
}
which will automatically allocate an array of objects to be stored inside the manager class so I can do things like:
-(void)doSomething
{
NSString *name = ((Test *)[self.aManager.tests objectAtIndex:0]).name;
}
I'm not even sure that initManager is the correct method to use - is there something built in that always gets called?
Firstly, let's look at the way we can initialize your Test class objects.
You can also write some initialization method for your Test class so instead of this:
Test example = [[Test alloc] init];
example.name = #"s";
you can write something like this:
Test example = [[Test alloc] initWithName:#"s"];
Please note that this is very common for initialization method to return newly created object, hence the initialization method usually returns 'id' type (not void).
This is the implementation for your test class which will be used in examples below.
.h file:
- (id)initWithName:(NSString *)aName;
.m file:
- (id)initWithName:(NSString *)aName
{
self = [super init];
if (self) {
_name = aName;
}
return self;
}
You can initialize your TestController class this way:
.h file:
- (id)initManager;
.m file:
- (id)initManager
{
self = [super init]; //always call the superclass init method when your class inherit from other class
if (self) { // checking if the superclass initialization was fine
_tests = [NSMutableArray array];
[_tests addObject:[[Test alloc] initWithName:#"s"]];
[_tests addObject:[[Test alloc] initWithName:#"l"]];
}
return self;
}
Or something like this:
- (id)initManager
{
self = [super init]; //always call the superclass init method when your class inherit from other class
if (self) { // checking if the superclass initialization was fine
_tests = [NSArray arrayWithObjects:[[Test alloc] initWithName:#"s"], [[Test alloc] initWithName:#"l"]];
}
return self;
}
Like the #Andrew said it is better to use alloc + init. Here are some examples of this syntax:
CGRect rect = CGRectMake(0, 0, 100, 100);
[[UIView alloc] initWithFrame:rect];
[[NSArray alloc] init]
This is the common way to initialize objects. Despite having this mechanism there are also some additional methods (which are in fact static functions) which give the programmer the nice way to initialize objects. Using them u don't have to write keyword 'alloc' so that the code is shorter and easier to read.
[NSArray array] //creates and returns empty array
[NSMutableArray array] //creates and return empty mutable array
[UIButton buttonWithType:UIButtonTypeContactAdd]; //creates and return button
first import header files of test, and test manager class, into controller class
#import Test.h
#import TestManager.h
then in controller class
-(void)initManager
{
TestManager *aTestManager = [TestManager new];
Test *test1 = [Test new];
Test *test2 = [Test new];
[aTestManager.tests addObject:test1];
[aTestManager.tests addObject:test2];
}
Let's start at the top. You probably can and should make the name readonly.
(Demos assume ARC is enabled)
#interface Test : NSObject
#property (nonatomic, readonly) NSString *name;
// and then simply initialize name:
- (instancetype)initWithName:(NSString *)pName;
#end
NSString properties should be copied:
#implementation Test
- (instancetype)initWithName:(NSString *)pName
{
self = [super init];
if (nil == self) return nil;
// copy the NSString:
// don't use getters/setters in initializers or -dealloc
_name = pName.copy;
return self;
}
#end
Similarly readonly:
#interface TestManager : NSObject
#property (nonatomic, strong, readonly) NSMutableArray *tests; // array of Test objects (array size unknown until runtime)
#end
#implementation TestManager
- (id)init
{
self = [super init];
if (nil == self) return nil;
// just initialize readonly tests:
_tests = NSMutableArray.new;
return self;
}
#end
Then TestController could probably use a readonly TestManager and borrow the form used above. Otherwise, it can be readwrite, if needed.
// don't declare/implement an instance method
// which has the prefix -init*, like initManager. renamed.
- (void)resetManager
{
// if readonly is ok, then just create it in the initializer.
// otherwise, if you need the ability to set the manager in the controller,
// then declare the property readwrite and:
self.testManager = TestManager.new;
// note: aManager is not a good name. renamed to testManager.
}
- (void)doSomething
{
assert(self.testManager && "did you forget to init the manager?");
Test * test = [self.testManager.tests objectAtIndex:0];
NSString * name = test.name;
...
}
This is far from covering all initialization cases in ObjC, but it is a start.
I have 2 classes, ClassA and ClassB
ClassA has one BOOL variable set to No.
I am trying to set this variable to Yes from ClassB, but can't seem to figure out how to.
Below is the code I am using which doesn't work, it is simply what I would've thought would work, I have stripped out the unnecessary information:
Class A:
ClassA.h
#interface AppDelegate : NSObject <NSApplicationDelegate> {
BOOL boolean;
}
- (id) init;
ClassA.m
- (id) init {
boolean = NO;
}
Class B:
ClassB.h
#import "ClassA.h"
- (IBAction) setBoolean: (id)sender;
ClassB.m
- (id) init {
ClassA * theClassA = [[ClassA alloc] init];
return self;
}
- (IBAction) setBoolean: (id)sender {
[theClassA boolean] = YES;
}
I hope this makes sense. I simply want to set the BOOL boolean in ClassA to YES from ClassB.
You can't assign a property like that ([object property] = value). The proper syntax is [object setProperty:value] or object.property = value.
I wouldn't call a variable boolean. Might be misleading. Even though it's not the keyword for a boolean variable in Objective-C it is in a lot of other languages.
And you have to return the initialized object (self) in your init method (you have an id return type, not void):
- (id) init {
self = [super init];
if (self) {
boolean = NO;
}
return self;
}
Also, you didn't specify an instance variable for theClassA in your ClassB implementation. You just create a local object and then leak it (you don't release it). Instead, declare it in your ClassB.h:
#class ClassA;
#interface ClassB : NSObject {
ClassA *theClassA;
}
- (IBAction)setBoolean:(id)sender;
#end
Then initialize it like this:
- (id) init {
self = [super init];
if (self) {
theClassA = [[ClassA alloc] init];
}
return self;
}
And don't forget to release it in dealloc:
- (void)dealloc {
[theClassA release];
[super dealloc];
}
And one last thing. Having a method - (IBAction) setBoolean: (id)sender in your ClassB implies that ClassB has a property called boolean, which is not the case. I recommend renaming that method and/or rethinking your class designs.
I'm trying to add a convenience constructor to my custom object.
Similar to [NSArray arrayWithArray:]
I know it involves a class method that returns an auto released object. I've been googling around but all I can seem to find is the definition of a convenience constructor but not how to write one.
Let's say you have the following:
#class PotatoPeeler : NSObject
- (instancetype)initWithWidget: (Widget *)w;
#end
Then to add a factory method, you'd change it to this:
#class PotatoPeeler : NSObject
+ (instancetype)potatoPeelerWithWidget: (Widget *)w;
- (instancetype)initWithWidget: (Widget *)w;
#end
And your implementation would simply be:
+ (instancetype)potatoPeelerWithWidget: (Widget *)w {
return [[[self alloc] initWithWidget: w] autorelease];
}
Edit: replaced id with instancetype. They are functionally identical, but the latter provides better hints to the compiler about the method's return type.
Generally my approach is the following: first I create a normal initializer method (instance method), then I create a class method that calls the normal initializer. It seems to me Apple uses the same approach most of the time. An example:
#implementation SomeObject
#synthesize string = _string; // assuming there's an 'string' property in the header
- (id)initWithString:(NSString *)string
{
self = [super init];
if (self)
{
self.string = string;
}
return self;
}
+ (SomeObject *)someObjectWithString:(NSString *)string
{
return [[[SomeObject alloc] initWithString:string] autorelease];
}
- (void)dealloc
{
self.string = nil;
[super dealloc];
}
#end
Is it safe to say that if a class member does not need getter or setter functions then there's no point in making them properties and synthesizing them?
Well, yes, but often properties can be helpful in the implementation itself even if the properties won't be set outside of the implementation.
For example, suppose you had
#interface SomeObject : NSObject {
NSThing *thing;
}
#end
#implementation SomeObject
- (id)init {
if((self = [super init]))
thing = [[NSThing someThing] retain];
return self;
}
- (void)someMethod {
if(thing)
[thing release];
thing = [[NSThing someOtherThing] retain];
}
// etc etc
#end
Why would you want to bother having to check if thing had been allocated, release thing, set it to something else, and then retain it again, when you could simply do:
- (id)init {
if((self = [super init]))
[self setThing:[NSThing someThing]];
return self;
}
- (void)someMethod {
[self setThing:[NSThing someOtherThing]];
}
If you don't want to make these properties accessible outside of your class, you can use a category
#interface SomeObject ()
#property (retain) NSThing *thing;
#end
in your .m file.