Initializing object in if condition - objective-c

I want to create a object but the type depends on the result of the if-condition:
if ([type isEqualToString:#"day"]) {
GraphDayView *graphv = [[GraphDayView alloc] initWithFrame:rect];
} else {
GraphMonthView *graphv = [[GraphMonthView alloc] initWithFrame:rect];
}
The problem is that graphv is out of scope, so I am not able to use it after the if-statement.
So I tried to declare it as an id:
id graphv;
if ([type isEqualToString:#"day"]) {
graphv = [[GraphDayView alloc] initWithFrame:rect];
} else {
graphv = [[GraphMonthView alloc] initWithFrame:rect];
}
But the problem now is, that the compiler doesn't know what kind of object grapv is. So:
graphv.backgroundColor = [UIColor whiteColor];
gives an error. Anyone an idea how to solve this one?

If they share the same superclass, then use that instead of id. Otherwise, create two variables and set them to nil:
GraphDayView *gdv = nil;
GraphMonthView *gmv = nil;
Then test after the if statement to see which one was initialized.

Make GraphDayView and GraphMonthView both subclasses of GraphCalendarView. Then make your backgroundColor a property of GraphCalendarView.
(Or, if your two classes are already subclasses of a UI class that implements backgroundColor then you're home free.)
Declare your variable as GraphCalendarView graphv;, and then proceed as before. If you need to use a property/method that's unique to of one or the other of your two derived classes then cast to that class first.

Try this,
id graphv;
if ([type isEqualoString:#"day"]) {
(GraphDayView *)graphv = [[GraphDayView alloc] initWithFrame:rect];
} else {
(GraphMonthView*)graphv = [[GraphMonthView alloc] initWithFrame:rect];
}

A lot of suggestions for a common superclass have been made which are most liekly suitable.
In the event they are not then you could use typcasts. However, typecasts may cause exceptions like unknown selecor or bad_exec. If you want to savely use typcasts then you should always check either isKindOfClass or respondsToSelector.
Sample:
id someClassObject; //or any other common superclass* instead of id
If ([something isTrue])
someClassObject = [[AClass alloc] init]; //assuming ARC. If not then you may want to retain/autorelease here too.
else
someClassObject = [[AClass alloc] init];
... //some code
if ([someClassObject isKindOfClass:[AClass class]])
[(AClass *) someClassObject methodOfAClass];
//
if ([someClassObject isKindOfClass:[AClass class]]) {
AClass *aClassTemp = (AClass *) someClassObject;
[aClassTemp methodOfAClass];
aClassTemp.propertyOfAClass = someValue;
}
if ([someClassObject respondsToSelector:#selector(methodOfBClass:))
[someClassObject perforformSelector:#selector(methodOfBClass:) withObject:[UIColor clearColor]];
Note the number of : following the selector name correlates to the number of parameters of the method. You may not find a suitable variance of performSelector for each possible method call. Especially as you can pass or return references to objects only.
Again, both of them I would only suggest if the common subclass is not suitable in your case.

Related

Objective-C Class definitions

Is it possible to alter the definition of a class in Objective-C?
For example, I have a function that creates objects (bullets), and those bullets are all the same. However, if given an event, say a power-up, I want to alter those bullets. In either size, or color, or what-have-you.
In my code, I have
playerBullet = [[PlayerBullet alloc] init];
But that always initializes the new bullet, regardless of 'power-up', as the standard, template bullet.
Is there a way that I adjust the class definition such that all new allocations of the PlayerBullet class come with the new value?
PlayerBullet.setProjectileColor:#"red";
Or is this approach a bad one?
The answer to your question as asked is No. However, what you are trying to do is very easily achievable with the proper design.
You should create a customer initialization method in your PlayerBullet class that takes an argument.
- (id) initWithProjectileColor:(NSString*)color
{
self = [self init];
if (self)
{
self.projectileColor = color;
}
return self;
}
So you could make a method and then call something like:
[[PlayerBullet alloc] initWithProjectileColor:#"red"];
Alternatively, you could create public properties and set them after creating a "blank" projectile.
call [[PlayerBullet alloc] initWithColor:#"red"] where you need it:
-(id) initWithColor:(NSString*) color{
self = [self init];
[self setProjecticeColor:colour];
return self;
}
There's no way to do this automatically. You could do it yourself with something like this. (Memory management not included.)
static NSString *DefaultProjectileColor = #"black";
+(void) setDefaultProjectileColor:(NSString *)color {
DefaultProjectileColor = color;
}
+(NSString *) defaultProjectileColor {
return DefaultProjectileColor;
}
-(id) init {
...
self.projectileColor = [PlayerBullet defaultProjectileColor];
...
}
-(void) gotPowerUp {
...
[PlayerBullet setDefaultProjectileColor:#"red"];
...
}

iphone ARC - how to set an object to nil?

I have some logic that test if an object is nil or not, how can I set it to nil?
Like:
// in some method
if (true){
[self myObj] = [[myObj alloc]init];
} else{
[self myObject] = nil; //??? How to do this with Automatic Ref. Counting
}
// elsewhere
if([self myObj]){
}
Your code is incorrect.
You need to use properties and assign values to them, like [self setMyObject:nil]; or [self setMyObj:[[myObj alloc] init]];.
Your code is wrong. Try:
self.myObject=nil;
//or
[self setMyObject:nil];
Also, make sure that myObject is a property in your class, otherwise using self won't work.
[self myObj] is not assignable, as it is not a lvalue. To fix this, either reference the underlying variable, e.g. self->myObj, or use [self setMyObj:] if you are using properties.
You are using a getter as a setter. That isn't going to work. It should be
[self setMyObj:[myObj alloc]init]];
And
[self setMyObj:nil];
Assuming you have implemented the setters. Under ARC you don't really need to if you are just accessing an ivar - you can access it directly and the reference counting will be done for you:
myObj = [MyObj alloc] init];
And
myObj = nil;
Will set and remove everything for you.

Objective C instance variable is released despite being retained

I have a marshmallow class which has (among other things) a CCSprite object as an instance variable.
here is the init method:
-(id) init
{
if((self = [super init]))
{
model = [[CCSprite spriteWithFile:#"marshmallow.png"] retain];
maxSpeed = 5; //160px per second (maxspeed * PTM_Ratio = px/second max)
gravity = 9.81; // in meters/sec^2
health = 3;
}
return self;
}
the variable is declared in another file as a global variable with the line:
Marshmallow *mainChar;
Later in the file, it is set (initiated/alloc'd) with this line:
mainChar = [[mainChar alloc] init];
while writing the previous line, xcode gave me a warning that Marshmallow might not respond to alloc. (I don't think that's related. just mentioning anything that seems wrong)
my problem is that the following line of code returns nil:
[mainChar getModel];
why does it return nil instead of the instance variable?
here is the getModel function:
-(CCSprite *)getModel
{
return model;
}
mainChar = [[mainChar alloc] init];
Shouldn't be
mainChar = [[Marshmallow alloc] init];
?
The message says an object from that class might not respond to it, not the class itself.
Your problem is in the initialization of your mainChar variable. The line you're looking for is this:
mainChar = [[mainChar alloc] init];
The warning you got is telling you that instances of type Marshmallow will not respond to the -alloc message. That is your problem: you want to call the +alloc class method instead, like so:
mainChar = [[Marshmallow alloc] init];
I think you want to do
mainChar = [[MarshMallow alloc] init];
instead of
mainChar = [[mainChar alloc] init];
The error message you got is very important.

Typecasting a TableViewController Class

I'm having a problem with either typecasting or object scope. I'm getting an uncaught exception:
// Create the object here so that it's scope is outside the `if` statement, right?
searchTableViewController *newViewController;
if (rowSelected) {
// Typecast the object to a searchTableViewController
(searchTableViewController *)newViewController ;
// Initialize and Allocate
newViewController = [[searchTableViewController alloc] initWithSearchBar:NO grouped:NO];
}else{
// Typecast the global object to a personViewController
(personViewController *)newViewController;
// Initialize and Allocate
newViewController = [[personViewController alloc] init];
}
// Act on the object: create a data model object and store it as a property, etc.
newViewController.myDataModel = [[dataModel alloc] initWithSelected:selectedField delegate:newViewController];
I have 2 similar ViewControllers searchTableViewController and personViewController. So I want to be able to instantiate either one or the other, using the same name, that way the rest of my code can act on the viewController using common properties and such.
This is causing a Terminating due to uncaught exception and regardless it seemed like the wrong way to do it anyway. I need help either in the typecasting department or I need help understanding how to declare the scope of these objects properly so I can work with them in and outside of the if statement.
Update
The easiest thing would be for me to write my code as follows. How can I declare, allocate and instantiate an object within an if statement and then have it accessible outside?:
if (rowSelected) {
searchTableViewController *newViewController = [[searchTableViewController alloc] initWithSearchBar:NO grouped:NO];
}else{
personViewController *newViewController = [[personViewController alloc] init];
}
// This will probably give an error since newViewController is not in the proper scope.
newViewController.myDataModel = [[dataModel alloc] initWithSelected:selectedField delegate:newViewController];
You want to declare your newViewController as the common subclass of the two objects you're possibly allocating. Probably UIViewController.
UIViewController *newViewController;
if (rowSelected) {
// Initialize and Allocate
newViewController = [[SearchTableViewController alloc] initWithSearchBar:NO grouped:NO];
}else{
// Initialize and Allocate
newViewController = [[PersonViewController alloc] init];
}
And the cast operation isn't doing anything when you use it in-place like that.
Edit - if both of those classes have common properties, like dataModel, then you can avoid warnings by creating a common base class that derives from UIViewController and which contains those properties. You'd then change the declaration of your view controller variable in the first line above to match the intermediate base class.
Later Edit — if you don't want to create an intermediate base class, you can do the following (newViewController still must be declared as a UIViewController):
if([newViewController respondsToSelector:#selector(setMyDataModel:)]) {
DataModel *dataModel = [[dataModel alloc] initWithSelected:selectedField delegate:newViewController];
[newViewController performSelector:#selector(setMyDataModel:) withObject:dataModel];
}
RE: Your Edit
id newViewController;
if (rowSelected) {
newViewController = [[searchTableViewController alloc] initWithSearchBar:NO grouped:NO];
}else{
newViewController = [[personViewController alloc] init];
}
// This will probably give an error since newViewController is not in the proper scope.
newViewController.myDataModel = [[dataModel alloc] initWithSelected:selectedField delegate:newViewController];

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];