Obj-C - Calling & setting values on ivars & instance methods - objective-c

I don´t understand what I might be doing wrong here since I am following an instruction book - on setting the value calling a method of an class. I have #imported it, and everything should be fine. But Xcode complains: no know class method selector for "setWeightInKilos and "setHeightInMeters". These instance methods are implemented in the "Person" class so I do not know why this happens.
Person *aPerson = [[Person alloc] init];
[Person setWeightInKilos:96];
[Person setHeightInMeters:1.8];
float bmi = [Person bodyMassIndex];

These are instance methods, not class methods. Therefore you should use the name of the variable aPerson to invoke them on the instance, rather than the name of the class Person:
Person *aPerson = [[Person alloc] init];
[aPerson setWeightInKilos:96];
[aPerson setHeightInMeters:1.8];
float bmi = [aPerson bodyMassIndex];
In general, all methods declared with a - in front take a variable with an instance of that class; methods declared with a + take the class name.
There is also an alternative syntax that you can use if heightInMeters and weightInKilos are declared as properties: you can write
aPerson.weightInKilos = 96;
aPerson.heightInMeters = 1.8;
This is only a different syntax for the same thing, though; the setter methods will be invoked in both cases.

In Objective C, there are two types of methods. Class methods, which do not act on an object, but instead usually perform some task related to that TYPE of object. For example, there are built in methods to test for network connections, device capabilities, etc. These are always denoted with a "+" sign, and cannot be called on instances of objects, only the class itself.
The other type, instance methods, are MUCH more common, and act on objects that you instantiate or create yourself. These are the methods that you write in the implementation file with a "-" prefix. They are things like getters, setters, or methods that you have written yourself.
In your case, you need to call these setter methods on the instance of your Person object, not the Person class itself. It doesnt make sense to set the height or weight of the entire class (nor is it possible), since every object will need to have its own unique height and weight.
You need to call:
Person *aPerson = [[Person alloc] init];
[aPerson setWeightInKilos:96];
[aPerson setHeightInMeters:1.8];
float bmi = [aPerson bodyMassIndex];

It looks like you want to set the instance variables called weight and height. So to fix it, do this:
Person *aPerson = [[Person alloc] init];
[aPerson setWeightInKilos:96];
[aPerson setHeightInMeters:1.8];

Related

Why should I not separate alloc and init?

The normal way to initialise and allocate in Objective-C is
NSObject *someObject = [[NSObject alloc] init];
Why is the following not practised?
NSObject *someObject = [NSObject alloc];
[someObject init];
The main problem is that you might end up using the wrong object.
init is special in many classes as it might just release the receiver and instead create a new object that resides at a different address. So your someObject then points to the wrong (uninitialized) instance.
There are a lot of framework classes that use the arguments of the init method to decide which kind of specialized subclass is best to use. This frequently happens with class clusters like NSString or NSArray but it can really happen with each kind of object.
One place where you can see this special behavior of initializers is ARC: It explicitly declares that the init family of methods eats up the receiver and returns a +1 retained object. This would not be necessary if initializers would just always return the receiver.
Of course you could fix your code by just doing another assignment:
NSObject *someObject = [NSObject alloc];
someObject = [someObject init];
This would fix the problem. But there's also no sense in doing it.
From the Object Initialization official documentation:
Because an init... method might return nil or an object other than the one explicitly allocated, it is dangerous to use the instance returned by alloc or allocWithZone: instead of the one returned by the initializer
Another reason from the same document is that:
Once an object is initialized, you should not initialize it again
given this example:
NSString *aStr = [[NSString alloc] initWithString:#"Foo"];
aStr = [aStr initWithString:#"Bar"];
where:
the second initialization in this example would result in NSInvalidArgumentException being raised.
Because it is less simple and more error-prone.
An allocated but not initialised object is useless, so it make sense to put allocation and initialisation in one line. If they are separated, there is more possibility for errors and bugs if the two lines are not directly after each other (perhaps after refactoring), which may lead to errors while trying to use an uninitialised object.
There simply isn't a single good reason to alloc and init in separate lines, and many reasons against it.
As per my understanding an allocated object makes no sense without it being initialized,
if you alloc an object first and then later plan to initialize it, there might be a case that you may forget to initialize the object and give a direct call to any of its instance method which would result in run time error.
Example:
NSString *str = [NSString alloc];
// Override point for customization after application launch.
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
NSLog(#"%ld",str.length);
When i run the above code i get this in my console
Did you forget to nest alloc and init?
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '*** -length only defined for abstract class. Define -[NSPlaceholderString length]!'
if i would do the below I would still get the exception as str is not being initialized because whatever is being initialized is not being consumed or pointed by str
[str init];
Hence if you want to do it in two lines it should be like this
NSObject *someObject = [NSObject alloc];
someObject = [someObject init];
But it's always better to keep them nested
NSObject *someObject = [[NSObject alloc]init];
If you plan on doing it on single line then use the new keyword which servers the purpose of allocation and initialization on a single line.
Example: YourClass *object_ofClass = [YourClass new];

Constructor method not being called properly

I made a class called Player.h. The constructor is such:
-(Player*) construct
{
health = 100;
width = 50;
height = 50;
return self;
}
And it is in my header file as -(Player*) construct;
To verify I call my getWidth method and the other getters and all return as 0
Is this the right way to make a constructor? I'm trying to incorporate better OOP practices and this is the first time in Obj-C I'm really using objects
Usually, you create objects in Objective-C by way of calling alloc/init, in your case [[Player alloc] init]. Just overwrite the init method in your class - it already has the right skeleton. Do not remove the self = [[super alloc] init] line.
If you want your object to be constructed, you need to allocate in initialize it. While you can call your method -construct, it's traditionally called -init or -initWith<Blah> where <Blah> is some information like a rectangle or other useful value. You'd create an object like this:
Player* newPlayer = [[Player alloc] init];

Will this work?

UILabel *testLbl = [[self alloc] init];
This is where the confusion started:
It’s usually better to use a variable other than self to refer to an instance inside a class
method:
+ (id)rectangleOfColor:(NSColor *)color {
id newInstance = [[Rectangle alloc] init]; // GOOD [newInstance setColor:color]; return [newInstance autorelease];
}
In fact, rather than sending the alloc message to the class in a class method, it’s often better to send alloc to self. This way, if the class is subclassed, and the rectangleOfColor: message is received by a subclass, the instance returned will be the same type as the subclass (for example, the array method of NSArray is inherited by NSMutableArray).
+ (id)rectangleOfColor:(NSColor *)color {
id newInstance = [[self alloc] init]; // EXCELLENT [newInstance setColor:color]; return [newInstance autorelease];
}
No, It'll cause a "UILable undeclared (first use in this function)" error.
No, it won't work. In your first line, you are sending the alloc message to an instance of a class. In the examples you copied out of Apple's documentation, they are sending alloc messages to the Class Rectangle. The difference is that your line is (apparently) inside an instance method, Apple's examples are inside class methods. There is a difference.
Like #Denis mentioned, you can do what you're trying to do by saying [[[self class] alloc] init], but in practice, don't do this. You'll almost never need the flexibility this offers and it will only muddy the intent of the new object.

init] as a factory method

I want to initialize an instance of one of the subclasses of a superclass depending on the arguments to init:
[[Vehicle alloc] initWithItinerary: shortWay]; // returns a bicycle
[[Vehicle alloc] initWithItinerary: longWay]; // returns a car
I can't find examples of code like this. I wonder if this is not idiomatic in Objective C, or I simply am not looking in the right places.
You could do this via a custom init method, but it'd be kind of tedious (you'd have to invoke [super init], but then call [self release], etc...). It'd be much simpler to create a class method on Vehicle and use that as your factory method. For example:
+ (id) vehicleWithItinerary:(id)someItinerary {
if ([someItinerary isAShortWay]) {
return [[[Bicycle alloc] initWithItinerary:someItinerary] autorelease];
} else if ([someItinerary isAMediumWay]) {
return [[[RocketPack alloc] initWithItinerary:someItinerary] autorelease];
} else if ([someItinerary isALongWay]) {
return [[[Car alloc] initWithItinerary:someItinerary] autorelease];
}
return nil;
}
Look at [UIButton buttonWithType:] for an example of how Apple does this. Instead of init, they use a static method of the base class to allocate an instance of the appropriate derived class.
You can also pass around Class objects. Maybe the itinerary knows the Class or class name to allocate. You can do something like this:
[[[itinerary classToAllocate] alloc] initWithItinerary:itinerary];
or
[[NSClassFromString( [itinerary classNameToAllocate] ) alloc] initWithItinerary:itinerary];
You are allowed to release self and create a new object in init, although this is rarely used. Just watch out for recursion.
-(id) initWithItinerary:(Itinerary *)inItinerary {
[self release]; // super init never called - safe if you wrote super classes
self = [[[inItinerary classToAllocate] alloc] init];
self.itinerary = inItinerary;
return self;
}
This is called a class cluster. Several Cocoa classes work this way, including NSArray and NSString. The object returned from NSArray's init methods is never the same object that received the message. It's not that common outside of Cocoa, though, just because it's usually more complicated than people want to bother with. Basically, you figure out what actual class you want to use in your initializer, create an instance of that class, release yourself and return the other instance.
You might want to add an enum to the header file:
typedef enum {Bike, Car, JetPack
} vehicleType
That way your initWithItinerary: method can simply be:
if(VehicleType == Bike)
{
//do bike stuff
}
else if(VehicleType == Car)
{
//do car stuff
}
Why not have a method as part of the "way" that gives you a vehicle of the appropriate type for the way. e.g.
e.g.
// Somwhere before you use them. Car and Bicycle are subclasses of Vehicle
[shortWay setAppropriateVehicleType: [Bicycle class]];
[longWay setAppropriateVehicleType: [Car class]];
// when you need a vehicle
Vehicle* vehicle = [[[shortWay appropriateVehicleType] alloc] init];

How to alloc a dynamic typed object

I have seen a lot of talk about dynamic typing in objective-c. But i haven't seen any examples of what i think it is supposed to be.
lets say I have a generic function that is supposed to juggle two objects (one gets allocated and the other gets freed) and the calling object attaches it self to the newly alloced object. Both are inherited from class0
Please feel free to interpret this however you want if you think it will explain something!!
If the class is picked at runtime, how do i deal with the arguments list (? is a placeholder for now)
How do i alloc a object who's class is not defined until runtime?
-(void) juggle:(?*)objclass1:(?*)objclass2{
? temp = [? alloc] init];
objclass1 = temp;
[temp release];
[objclass2.view removefromsuperview];
[self.handle insertsubview:objclass1.view];
}
I have no idea what the code you have there is trying to do, it is not syntactically valid, and manipulating views has nothing to do with your questions. Anyway, if you really don't know the type you generally use "id" which is type cast to a "void *" for codegen. It has the special property that it is assumed to receive any message, so it does not trigger compiler warnings for unknown messages.
In order to instantiate a class you just need to be holding the "Class" object for it. In Objective C all instances of a class refer to a Class object (the isa pointer in the legacy runtime), which also responds to methods. So in other words, in the following code:
NSArray *myObject = [[NSArray alloc] init];
NSArray is actually an object. So this will generate equivalent code results:
Class myClass = [NSArray class];
NSArray *myObject = [[myClass alloc] init];
or even
Class myClass = NSClassFromString(#"NSArray");
NSArray *myObject = [[myClass alloc] init];
Which uses the function NSClassFromString which walks into the runtime and finds a class with the name you pass in.
All objects return their class if use the class getter, so to instantiate an object that is the same class as an existing object like this:
- (void) leakObjectWithSameClassAs:(id)object {
[[[object class] alloc] init];
}
This is what i have now
- (void)flipfromv1tov2:(UIViewController*)v1:(NSString*)nib1:(UIViewController*)v2{
if(v1 == nil)
{
UIViewController *newview = [[[v1 class] alloc] initWithNibName:nib1 bundle:nil];
v1 = newview;
[newview release];
}
[v2.view removeFromSuperview];
[self.view insertSubview:v1.view atIndex:0];
}
I cannot verify it yet because I have a linking problem...I added this func to my root controller but for some reason I get a warning that the function is implicitly declared. And the build fails because the function call never get linked to anything