3

In my program I have a class, say ClassA. I'd like to create a derived class, say ClassB. My program has functions returning instances of ClassA and in certain cases I'd like to use these returns to create an instance of ClassB. Doing the naive downcasting doesn't lead to any compiler errors, but runtime errors creep in. This seems to be related to issues about casting pointers vs. objects -- at any rate, from what I've read, this was the wrong thing to do in this case.

I then attempted to mimic the copy-constructors of C++, but variables that were copied passed out of scope or were released, leading again to run-time errors.

I also considered using categories but this doesn't seem right for two reasons:

  1. First and foremost, I can't add new class members with categories and things will get a little convoluted if I can't do this.
  2. As near as I can tell the methods added through categories become available to all instances of the class. While this isn't, strictly speaking, a problem it doesn't sit well with me since certain aspects of the methods I want to add would break in a general setting; i.e. they are somehow specific to what I need to do with these types of objects.

I'm still learning Objective-C. I know there are some similar questions in the forum but the answers either led to categories and dead-ended or dead-ended without much help so I felt I should simply repost.

jscs
  • 63,694
  • 13
  • 151
  • 195
Paul
  • 31
  • 1
  • 2

4 Answers4

4

In my program I have a class, say ClassA. I'd like to create a derived class, say ClassB.

@class MONClassA;

@interface MONClassB : MONClassA
{
    int anotherVariable;
}

//...
- (id)initWithMONClassA:(MONClassA *)classA anotherVariable:(int)anotherVar;

@end

My program has functions returning instances of ClassA and in certain cases I'd like to use these returns to create an instance of ClassB.

same way you'd do it in c++ (as it sounds like that's your background), promote the type with a new instance:

//...
MONClassA * a = SomeLibFunction();
MONClassB * b = [[MONClassB alloc] initWithMONClassA:a anotherVariable:???];
//...

Doing the naive downcasting doesn't lead to any compiler errors, but runtime errors creep in. This seems to be related to issues about casting pointers vs. objects -- at any rate, from what I've read, this was the wrong thing to do in this case.

correct - it is the wrong thing to do.

I then attempted to mimic the copy-constructors of C++, but variables that were copied passed out of scope or were released, leading again to run-time errors.

see the above implementation to see how to promote a type in objc. this of course assumes you'll also define an appropriate implementation for initialization (equivalent to your cpp constructor and copy constructor).

I also considered using categories but this doesn't seem right...

then your instinct is probably right. this language feature is misused quite often.

justin
  • 104,054
  • 14
  • 179
  • 226
1

It sounds like there is a method that returns an instance of ClassA, but you'd like it instead to return an instance of ClassB.

The reason that casting the return value doesn't work is because the instance returned by the method isn't actually an instance of ClassB - it's really just an instance of ClassA. Sure the cast itself will "work", but the underlying instance doesn't have the data necessary in order to properly handle the members of ClassB.

Assuming that you can't (or don't want to) change the method to return an instance of ClassB, one option (other than Categories) would be to modify ClassB to take an instance of ClassA. ClassB would override all of the methods implemented by ClassA, and defer to the instance of ClassA passed in originally. For example:

@implementation ClassB
- (id)initWithClassA:(ClassA*)a
{
    self = [super init];
    if(!self)
    {
        return nil;
    }

    myClassA = [a retain];

    return self;
}

- (void)dealloc
{
    [myClassA release];
}

- (void)ClassAMethod1
{
    [myClassA ClassAMethod1];
}

- (int)ClassAMethod2
{
    return [myClassA ClassAMethod2];
}

- (void)ClassBMethod1
{
    // implement the method here
}
@end

You would then create an instance of ClassB, using the instance of ClassA returned by the original method. This essentially wraps the original instance of ClassA, turning it into an instance of ClassB.

Chuck
  • 234,037
  • 30
  • 302
  • 389
Andy
  • 30,088
  • 6
  • 78
  • 89
  • This seems like it could work. My only concern with it is that since I didn't write ClassB its structure could change in later revisions and I'd then have to go back to rewrite this class. While I don't expect that to be a problem at the moment I'm curious as to if there is a way around it. – Paul May 29 '11 at 20:57
  • @Paul: "My only concern with it is that since I didn't write `ClassB` ...". In your question, you state "`I'd like to create a derived class, say ClassB`", which makes it sound as though you **do** have complete control over it. Perhaps you could edit your question to make it more clear? – NSGod May 30 '11 at 00:05
1

Let's say ClassA, ClassB, and ClassC are defined as follows:

@interface ClassA : NSObject {

}
- (void)methodA;
@end

@interface ClassB : ClassA {

}
- (void)methodB;
@end

@protocol ClassCProtocol <NSObject>
- (void)protocolMethodC;
@end

@interface ClassC : ClassA <ClassCProtocol> {

}
@end

To make things interesting, I also defined a @protocol named ClassCProtocol that inherits from the <NSObject> protocol, as well as a ClassC object that is a subclass of ClassA and conforms to the <ClassCProtocol> protocol (all that really means is that any object that conforms to the protocol is guaranteed to implement the -protocolMethodC method).

The first thing to note is that in Objective-C, there isn't really a such thing as a derived class in the same sense as there is in C++: there's only single inheritance, so we generally talk about ClassB being a subclass of ClassA, or ClassC being a subclass of ClassA.

Then take the following code, assuming MDLoadObject() is a function that will return an instance of ClassA, ClassB, or ClassC based on whatever circumstances:

ClassA *MDLoadObject() {
    ClassA *object = nil;
    if (...) {
       object = [[[ClassA alloc] init] autorelease];
    } else if (...) {
       object = [[[ClassB alloc] init] autorelease];
    } else {
       object = [[[ClassC alloc] init] autorelease];
    }
    return object;
}

@interface MDAppController : NSObject {

}
- (void)loadObject:(id)sender;
@end

@implementation MDAppController

- (void)loadObject:(id)sender {
   ClassA *instanceOfClassABorC = MDLoadObject();

   if ([instanceOfClassABorC isKindOfClass:[ClassB class]]) {
       [(ClassB *)instanceOfClassABorC methodB];
   } else if ([instanceOfClassABorC isKindOfClass:[ClassC class]]) {
       [(ClassC *)instanceOfClassABorC protocolMethodC];
   } else if ([instanceOfClassABorC respondsToSelector:@selector(protocolMethodC)) {
       [(ClassC *)instanceOfClassABorC protocolMethodC];
   } else {

   }
}
@end

Since the highest common ancestor of classes ClassB and ClassC is ClassA, we define the MDLoadObject() function to return an instance of ClassA. (Remember that in single inheritance all instances of ClassB and ClassC are also guaranteed to be instances of ClassA).

The -loadObject: method, then, shows the dynamism of Objective-C, and several ways you can inquiry about what type of object was returned from the MDLoadObject() function at this point in time. Note that the -loadObject: method could also be written without the casts and it would run just fine at runtime:

- (void)loadObject:(id)sender {
   ClassA *instanceOfClassABorC = MDLoadObject();

   if ([instanceOfClassABorC isKindOfClass:[ClassB class]]) {
       [instanceOfClassABorC methodB];
   } else if ([instanceOfClassABorC isKindOfClass:[ClassC class]]) {
       [instanceOfClassABorC protocolMethodC];
   } else if ([instanceOfClassABorC respondsToSelector:@selector(protocolMethodC)) {
       [instanceOfClassABorC protocolMethodC];
   } else {

   }
}
NSGod
  • 22,699
  • 3
  • 58
  • 66
0

Have you tried using the id type? An object of any type can be assigned to it.

Ewe
  • 86
  • 2
  • I'm not really sure what you mean by this. Perhaps I'm just being dense, could you provide some sort of example? – Paul May 29 '11 at 20:57