En-têtes segmentés

Les fichiers d'en-têtes générés par J2ObjC sont divisés en segments et peuvent être inclus un segment à la fois. Un segment est créé pour chaque type Java traduit, de sorte que chaque classe interne possède son propre segment. Les macros de préprocesseur permettent d'indiquer au compilateur de ne lire qu'un segment particulier lorsque vous incluez l'en-tête.Les en-têtes segmentés résolvent le problème d'inclusion de cycles dans les en-têtes générés par J2ObjC, décrits en détail ci-dessous.

Ce que vous devez savoir

  • Utilisez #include au lieu de #import pour inclure les en-têtes générés par J2ObjC.
    • L'utilisation de #import avec des en-têtes segmentés est problématique, car le compilateur ignore la lecture de l'en-tête s'il l'a déjà vu. Toutefois, comme l'en-tête est segmenté, il est possible qu'il n'ait pas été entièrement analysé par le compilateur la première fois.
  • Les en-têtes segmentés peuvent être désactivés à l'aide de l'option --no-segmented-headers.

Inclusions circulaires

Les fichiers d'en-tête générés par J2ObjC doivent utiliser des déclarations d'inclusion et de transfert pour résoudre les informations de type nécessaires. Les déclarations de transfert sont utilisées autant que possible, mais les inclusions sont nécessaires pour les types étendus ou implémentés, car le compilateur exige la déclaration de type complète.

Il est possible de générer des cycles d'inclusion dans les fichiers d'en-tête générés par J2ObjC. Pour obtenir un tel cycle, nous avons besoin d'une classe du fichier A qui étend une classe du fichier B et d'une classe du fichier B qui étend une classe du fichier A. Il s'agit d'un scénario peu probable, mais qui se produit dans le codebase de Guava (et ailleurs).

Une solution naturelle à ce problème peut consister à émettre un fichier d'en-tête distinct pour chaque type de Java rencontré dans un fichier .java. Cependant, J2ObjC est conçu pour être utilisé comme outil de compilation, et tout système de compilation de qualité s'appuie sur des sorties prévisibles pour chaque entrée. Il est donc nécessaire que chaque fichier .java produise exactement un fichier .h et un fichier .m.

Exemple

Foo.java:

class Foo extends Bar {}

Bar.java:

class Bar {
  static class Baz extends Foo {}
}

Foo.h (non segmenté):

#ifndef _Foo_H_
#define _Foo_H_

#include "Bar.h"
#include "J2ObjC_header.h"

@interface Foo : Bar
- (instancetype)init;
@end

#endif // _Foo_H_

Bar.h (non segmenté):

#ifndef _Bar_H_
#define _Bar_H_

#include "Foo.h"
#include "J2ObjC_header.h"

@interface Bar : NSObject
- (instancetype)init;
@end

@interface Bar_Baz : Foo
- (instancetype)init;
@end

#endif // _Bar_H_

Notez que Foo.h inclut Bar.h et Bar.h inclut Foo.h. Par conséquent, la compilation de ces en-têtes échoue:

../dist/j2objcc -c Foo.m
In file included from Foo.m:6:
In file included from ./Bar.h:9:
./Foo.h:12:18: error: cannot find interface declaration for 'Bar', superclass of 'Foo'
@interface Foo : Bar
~~~~~~~~~~~~~~   ^

Vous trouverez ci-dessous les versions segmentées de Foo.h et Bar.h, qui seront compilées sans erreur.

Foo.h (segmenté):

#include "J2ObjC_header.h"

#pragma push_macro("Foo_INCLUDE_ALL")
#if Foo_RESTRICT
#define Foo_INCLUDE_ALL 0
#else
#define Foo_INCLUDE_ALL 1
#endif
#undef Foo_RESTRICT

#if !defined (_Foo_) && (Foo_INCLUDE_ALL || Foo_INCLUDE)
#define _Foo_

#define Bar_RESTRICT 1
#define Bar_INCLUDE 1
#include "Bar.h"

@interface Foo : Bar
- (instancetype)init;
@end

#endif

#pragma pop_macro("Foo_INCLUDE_ALL")

Bar.h (segmenté):

#include "J2ObjC_header.h"

#pragma push_macro("Bar_INCLUDE_ALL")
#if Bar_RESTRICT
#define Bar_INCLUDE_ALL 0
#else
#define Bar_INCLUDE_ALL 1
#endif
#undef Bar_RESTRICT

#if !defined (_Bar_) && (Bar_INCLUDE_ALL || Bar_INCLUDE)
#define _Bar_

@interface Bar : NSObject
- (instancetype)init;
@end

#endif

#if !defined (_Bar_Baz_) && (Bar_INCLUDE_ALL || Bar_Baz_INCLUDE)
#define _Bar_Baz_

#define Foo_RESTRICT 1
#define Foo_INCLUDE 1
#include "Foo.h"

@interface Bar_Baz : Foo
- (instancetype)init;
@end

#endif

#pragma pop_macro("Bar_INCLUDE_ALL")