7

I have a function that I want to operate on two different custom objects. My first thought was to accept the argument as an (id) and operate on the id object. I can't quite seem to figure out how to do that, however.

Both classes (say apples and oranges) have interface variables:

NSDecimalNumber *count;

I want to do something similar to this:

-(NSDecimalNumber*)addCount:(id)addObject{

    return [count decimalNumberByAdding:addObject.count];
}

I can't seem to figure out the syntax to make that happen. Is this the proper approach, or would it be better to subclass (from say a fruit class) and operate on the parent class?

-(NSDecimalNumber*)addCount:(Fruit*)addFruit{

    return [count decimalNumberByAdding:addFruit.count];
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jason George
  • 6,992
  • 8
  • 44
  • 60

5 Answers5

12

While you can send a message to any object (id) - property accessors require that the compiler be aware of the type you are dealing with - this is because property accessors are syntactic sugar around calling specific getter and setter methods.

You have a few of ways of working around this:

  1. Instead of accessing the count property, call the corresponding [getCount] methods.

  2. If the different classes have different versions of this method, you can use a runtime type check:

  3. Provide a base class for both types so that you can pass in something more specific than (id).

  4. Define and implement a Protocol that both objects implement that defines a count property (or method).

Example of a dynamic type check:

if( [object isKindOfClass:[Apple Class] )
   // call one overload of getCount
else if( [object isKindOfClass:[Orange Class] )
   // call another overload of getCount

Personally, I favor strong typing in my code because it makes it easier to understand the intent. It also allows the IDE to support your coding effort with intellisense, static analysis, and refactoring features. So, in your case, I would use either #3 or #4 as an approach - depending on whether inheritance is really appropriate for the problem.

LBushkin
  • 129,300
  • 32
  • 216
  • 265
  • 1
    I'm a fan of strong typing myself (I went with #4 FYI). Thanks so much for the information; this is exactly what I was looking for! – Jason George Jul 31 '09 at 02:30
  • If you really need strong typing, you might have a better time with a static language like C++. It is a shame that properties behave in this asymmetric manner. At the very least there should be a compiler flag to allow symmetric compilation behavior between property syntax and receiver-method syntax. – ctpenrose Jan 13 '11 at 23:36
  • @ctpenrose Obj-C can be just as statically typed as the next language if you want it to be. – kubi May 13 '11 at 18:57
8

You should try not to access instance variables from another class.

In Objective-C it's enough that the two objects respond to the same selector (say count), however that would give you a compiler warning.

There are two ways you can get rid of this warning: either by subclassing from a common Fruit class or by having your two classes conform to a protocol. I'd go with the protocol:

@protocol FruitProtocol

- (NSDecimalNumber *)count;

@end

@interface Orange : NSObject<FruitProtocol>
@end

@interface Apple : NSObject<FruitProtocol>
@end

Then your method can look like this:

-(NSDecimalNumber*)addCount:(id<FruitProtocol>)addFruit {
    return [count decimalNumberByAdding:[addFruit count]];
}

Here you are saying that your addCount expects any object that conforms to the FruitProtocol protocol, and hence can respond to the count selector, so the compiler will accept it.

pgb
  • 24,813
  • 12
  • 83
  • 113
  • wrt/ "You can't access instance variables from another class." You most certainly can. If you statically type an object, ie `MYObject *object;`, then you can access its instance variables from any class with the `->` operator. For example, assuming `MYObject` had an `int` ivar named `count`, you could use `int myCount = object->count;` to access it. The protections provided by `@private`, etc, are cosmetic only, even in ObjC2. – johne Jul 30 '09 at 21:58
  • This is great! Your code sample made it easy for me to implement without any further digging. Thanks so much! – Jason George Jul 31 '09 at 02:32
  • You are right johne, I'll change it to "you should not". Thank you – pgb Jul 31 '09 at 12:08
1

The fact that you are trying to access 'addFruit.count' is the problem. The dot syntax is only for properties declared with @property (or for structs). If you change it to

[addFruit count]

and add

-(NSDecimalNumber*)count
{
     return [[count retain] autorelease];
}

to each class, then it would work. However, you will notice you'll get a warning saying 'id' may not respond to the 'count' message, and unless you can be absolutely sure the items sent to this method implement a 'count' method, this is a problematic approach.

I agree with pgb's approach. You should define a protocol, and declare both classes to implement that protocol. This eliminates the problem of not knowing whether the object will respond to 'count' or not, as you now have a 'contract' of sorts.

If you want to keep the dot syntax with a property, you can declare it in the protocol:

@protocol FruitProtocol

@property(readonly) NSDecimalNumber * count;

- (NSDecimalNumber *)count

@end

and then, your function would be:

-(NSDecimalNumber*)addCount:(id<FruitProtocol>)addObject{

    return [count decimalNumberByAdding:addObject.count];

}
bobDevil
  • 27,758
  • 3
  • 32
  • 30
0

You're sending the message to count, what is count? id is a pointer to any type of object. If you expect the object to have a count property, then you should only be able to pass in an Array (or some other type restriction).

-(NSDecimalNumber*)addCount:(NSArray*) Object{

return [count decimalNumberByAdding: [Object count]]; 

}
Gordon Gustafson
  • 40,133
  • 25
  • 115
  • 157
0

As I understand it, id does not have any methods or variables associated with it because it is a generic pointer that does not refer to any specific class. This page has some good info on ids if you scroll down a bit.

anObject this will not have a count variable, which is why your first attempt won't work. Creating a base class and using that as a parameter to the method seems like the best idea to me.

Jamison Dance
  • 19,896
  • 25
  • 97
  • 99