Management of classes and inheritance hierarchies

The Management of classes and inheritance hierarchies section of this chapter includes the following line of code:

Method *methodList = class_copyMethodList(cls, &methodCount);

I was a bit curious about the Method type since here it appears to hold multiple values. Apple’s documentation about it was not very helpful; it just says, An opaque type that represents a method in a class definition.

I guess I just wanted to clarify that this Method type basically acts like an array that holds multiple values of type… what, exactly? Other Method types?

P.S.
What’s with the towel references in the book? An inside joke? :slight_smile:

methodList is a pointer to (or address of) an array of Method objects. You can access the individual methods in that array, but you can’t do much with them because they are opaque. You need a function that knows how to decode a method.

For example:

for (int i = 0; i < methodCount; ++i) {
      Method * method = methodList [i];
      Signature * signature = method_getSignature (method);
      ...
}

Signature and method_getSignature, in the code above, are not real; they are imaginary creatures concocted for the example.

Ah, I get it now. Thanks for clearing that up. I guess this line in the book would also be an example of a function that can decode a method:

    Method currentMethod = methodList[m];
    SEL methodSelector = method_getName(currentMethod);

I’m still struggling with the idea of what it means for a type to be “opaque”. Here is the best answer I’ve found so far for explaining it to a newcomer to programming:

Here it is in a nutshell.

Let’s say we are working with rational numbers. We define a type for them named RationalNumber and put it in the header file RationalNumber.h

RationalNumber.h

#ifndef RationalNumber_h
#define RationalNumber_h

struct RationalNumber {
    long numerator;
    long denominator;
};

typedef struct RationalNumber RationalNumber;

#endif /* RationalNumber_h */

Now that we have the type, we use it to compute the sum of two rational numbers.

// Two rational numbers
RationalNumber r1 = {1, 5};
RationalNumber r2 = {3, 5};

// Add them
RationalNumber sum = {r1.numerator + r2.numerator, r1.denominator};

That was easy to do because the denominators of both r1 and r2 are the same. If they were different, we would have to do more work to handle the delicate details.

Rather than directly handling the numerators and denominators of rational numbers when adding them, we can define a function to do all the hard work required. Whenever we add two numbers, we just use that function.

So we put the declaration of the function in the header file, along with the function that properly initialises a rational number.

RationalNumber.h

#ifndef RationalNumber_h
#define RationalNumber_h

#ifndef RationalNumber_h
#define RationalNumber_h

struct RationalNumber {
    long numerator;
    long denominator;
};

typedef struct RationalNumber RationalNumber;

RationalNumber RationalNumber_init (const long numerator, const long denominator);

RationalNumber RationalNumber_add (const RationalNumber r1, const RationalNumber r2);

#endif /* RationalNumber_h */

Now we can add any two rational numbers without having to worry about the delicate details.

// Two rational numbers
RationalNumber r1 = RationalNumber_init (1, 7);
RationalNumber r2 = RationalNumber_init (3, 35);

// Add them
RationalNumber sum = RationalNumber_add (r1, r2);

We can also define functions for other possible operations such as multiplication and division.

RationalNumber.h

#ifndef RationalNumber_h
#define RationalNumber_h

#ifndef RationalNumber_h
#define RationalNumber_h

struct RationalNumber {
    long numerator;
    long denominator;
};

typedef struct RationalNumber RationalNumber;

RationalNumber RationalNumber_init (const long numerator, const long denominator);

RationalNumber RationalNumber_add (const RationalNumber r1, const RationalNumber r2);

RationalNumber RationalNumber_multiply (const RationalNumber r1, const RationalNumber r2);

RationalNumber RationalNumber_divide (const RationalNumber r1, const RationalNumber r2);

#endif /* RationalNumber_h */

Now to make the transition to an opaque type, we remove the declaration of the struct RationalNumber from the header file, but we leave the typedef intact.

RationalNumber.h

#ifndef RationalNumber_h
#define RationalNumber_h

#ifndef RationalNumber_h
#define RationalNumber_h

typedef struct RationalNumber RationalNumber;

RationalNumber * RationalNumber_init (const long numerator, const long denominator);

void RationalNumber_release (const RationalNumber *);

RationalNumber * RationalNumber_add (const RationalNumber *, const RationalNumber *);

RationalNumber * RationalNumber_multiply (const RationalNumber *, const RationalNumber *);

RationalNumber * RationalNumber_divide (const RationalNumber *, const RationalNumber *);

#endif /* RationalNumber_h */

RationalNumber has now become an opaque type.

Also notice that we now use pointers to the opaque type in the header file because the definition of the actual type itself is no longer available there.

We hide the details of the opaque type in implementation files along with the function definitions. This means the details can safely be changed without having to change other code that uses rational numbers.

RationalNumber.c

#include "RationalNumber.h"

struct RationalNumber {
    long numerator;
    long denominator;
};

RationalNumber * RationalNumber_init (const long numerator, const long denominator) {
   ...
}

void RationalNumber_release (const RationalNumber * r) {
   ...
}

RationalNumber * RationalNumber_add (const RationalNumber * r1, const RationalNumber * r2) {
   ...
}
...
...
...

Thank you for the example. That helps clear it up some more.