2

I have two classes ClassA and Class B (they are viewControllers).
Class A is a delegate of classB.
ClassA "laucnhes" and instance of ClassB.
ClassB call methods on classA.

Let's say it's :

#import "ClassB.h"

@interface ClassA : NSObject {
    ClassB* subController;
}

- (void) doThis;

-------------------------------

#import "ClassA.h"

@interface ClassB : NSObject {
    ClassA* delegate;
}

-------------------------------

@implementation ClassB

- (void) someMethod {
   AnObject* myObj = [self.delegate.arr objectAtIndex:8];
   [self.delegate doThis];
}

Doing that way, A must import B, and B must import A.

If B do not import A (with just @class A), there is a compile error for the used attribute from A.
If B imports A, there is a compile error on the ClassA* delegateline.

Why do I have those compile errors? Doesn't #import protect again recursive calls ?

I don't need a solution to solve that problem, I know how I may do this.
But I wonder why my #import cause those problems. These are not #includes...

Peter Hosey
  • 93,914
  • 14
  • 203
  • 366
Oliver
  • 21,876
  • 32
  • 134
  • 229

4 Answers4

6

In .h files prefer @class to #import. Both can then be imported in the .m implementation files.

// ClassA.h -------------------------------
@class ClassB;
@interface ClassA : NSObject {
    ClassB* subController;
}

- (void) doThis;

// ClassB.h -------------------------------
@class ClassA;
@interface ClassB : NSObject {
    ClassA* delegate;
}

// ClassB.m -------------------------------
#import "ClassA.h"
#import "ClassB.h"

@implementation ClassB

- (void) someMethod {
   AnObject* myObj = [self.delegate.arr objectAtIndex:8];
   [self.delegate doThis];
}

Using @class statements instead of #import also reduces dependencies and makes the remaining ones more clear. It can also speed up compile times.

zaph
  • 108,117
  • 19
  • 176
  • 215
  • Doubtful on compile times. If you're forward declaring the class, you're eventually including it's header. Whether you read and process that file now and read it from cache later, or read it once later, is going to be very small difference.. – Nektarios Sep 29 '11 at 19:39
  • @Nektarios you're right that eventually you'll have to compile the header, but since #import pastes all of the text in from the header to its destination, each import compiles the header file one extra time. Not a huge deal for minimal headers, but can be an issue for complex files being imported many times. – matthias Sep 29 '11 at 19:45
  • @CocoaFu : I understand that it prefers that, but that's not the question. I know that. But #import is supposed to protect against those type of recursive call, and it seems that it don't. – Oliver Sep 29 '11 at 21:44
  • The files are not included recursively, that is why one is not included thus the declaration is not seen and there is a compiler error. BUt there is guesswork here because the error is not given in the question. – zaph Sep 29 '11 at 22:14
  • @Nektarios: In large projects, you can get noticeable gains in compile times with forward declarations. If `ClassA` has a `ClassB*` member and forward declares `ClassB`, any file that imports `ClassA.h` but doesn't need to use any of the `ClassB` stuff can avoid pulling in `ClassB.h` (and any of the headers that that imports as well). – Adam Rosenfield Sep 29 '11 at 22:32
  • @Adam : I understand, but those casse should be arare ? Why would you declare a member you don't use its stuff ? – Oliver Sep 30 '11 at 06:36
  • If you're doing those kinds of forward declarations just to use the data opaquely, just use `id` – Nektarios Sep 30 '11 at 15:05
  • `id` has no compile-time type checking, I like the compiler to catch my errors. – zaph Sep 30 '11 at 15:16
  • @Oliver: Not as rare as you'd think in a large project with millions of lines of code in thousands of source files. Sometimes all you want to do is call one method on a particular object, you don't need to access any of its data members or other functions. So, if that object has data members which are pointers to forward declared types, you can call the method you want without having to include other header files for types you don't care about. – Adam Rosenfield Sep 30 '11 at 15:50
5

Why do I have those compile errors? Doesn't #import protect again recursive calls ?

#import protects against repeatedly importing the same header into the same module, whether by circular includes/imports or not. It protects against that by not letting you do it: Only the first #import of a header works; subsequent #imports of the same header are ignored.

In a circular #include situation, the preprocessor would go around the circle some number of times and then fail the build before you even get to compilation. Using #import prevents the preprocessor from getting wedged and lets the preprocessor succeed, but circular-#import code is still dodgy at best and usually will not compile.

So, on to your specific situation.

For the code you showed in your question, @class will work in either or both headers, and indeed you should use it in both. You'll also need to #import both headers in both .m files.

If B do not import A (with just @class A), there is a compile error for the used attribute from A.

If you mean “there is a compile error at each point where I use that attribute of type ClassA *”, then yes: You can't talk to that object because you haven't imported its interface, so the compiler doesn't know what messages you can send to a ClassA instance. That's why you need to import its interface.

If B imports A, there is a compile error on the ClassA* delegateline.

If both headers import each other, then you have this:

ClassA.m:
    ClassA.h
        ClassB.h
            ClassA.h (ignored because this was already imported by ClassA.m)
ClassB.m:
    ClassB.h
        ClassA.h
            ClassB.h (ignored because this was already imported by ClassB.m)

There is no way this can work without one interface preceding the other without the other interface preceding it. That's the circle you're running into—the circle that #import exists to break. #include allows the circle, and thereby gets wedged:

ClassA.m:
    ClassA.h
        ClassB.h
            ClassA.h
                ClassB.h
                    ClassA.h
                        ClassB.h
                            ClassA.h
                                ClassB.h
                                    ClassA.h
                                        ClassB.h
                                            ClassA.h
                                                ClassB.h
                                                    (fails at some point)

Hence #import.

So you cannot import each header from the other. Hence @class.

But you still need to import each header from each module. That is, in fact, exactly what you need to do: Use @class in each header and use #import (on both headers) in each module.

Peter Hosey
  • 93,914
  • 14
  • 203
  • 366
1

This compile complaint can be avoided by declaring

@class ClassB;

in the .h file. The ClassB.h can then be included into the .m file.

So you are right on that one. Contrary to urban myth, #imports work pretty much like #includes in the sense that the compiler has to check the file.

See this (duplicate?) question for your philosophical problem.

Community
  • 1
  • 1
Mundi
  • 77,414
  • 17
  • 110
  • 132
  • The main difference between #include and #import is that #import will happen only once, no guard defines are needed. Of course #import does not help this problem. – zaph Sep 29 '11 at 19:25
  • @Mundi : I don't see the answer of my phylosophical problem that IS the question. Why does that recursive calls generate copile errors ? i've read the question you point but that does not help. – Oliver Sep 29 '11 at 21:43
0

I think you'll find that #import only protects against multiple inclusion once it has already been successfully included once, so to speak.

ie, in your case, it hasn't successfully imported classa.h before it is asked to import it again, so it does so.

Jeff Laing
  • 913
  • 7
  • 13