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. Chaque classe interne aura donc son propre segment. Les macros de préprocesseur permettent d'indiquer au compilateur de ne lire qu'un segment particulier lors de l'inclusion de l'en-tête. Les en-têtes segmentés résolvent le problème des cycles d'inclusion dans les en-têtes générés par J2ObjC, décrit en détail dans ce document.
Que devez-vous 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.
- L'utilisation de
- Les en-têtes segmentés peuvent être désactivés à l'aide de l'indicateur
--no-segmented-headers
.
Contenu de la circulaire
Les fichiers d'en-tête générés par J2ObjC doivent utiliser des inclusions et des déclarations de forward pour résoudre les informations de type nécessaires. Les déclarations avant sont utilisées autant que possible. Toutefois, les inclusions sont nécessaires pour les types étendus ou implémentés, car le compilateur nécessite la déclaration complète du type.
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 dans le fichier A qui étend une classe dans le fichier B, et d'une classe dans le fichier B qui étend une classe dans le fichier A. Il s'agit d'un scénario peu probable, mais il se produit dans le code source de Guava (et ailleurs).
Une solution naturelle à ce problème consiste à émettre un fichier d'en-tête distinct pour chaque type Java rencontré dans un fichier .java. Toutefois, J2ObjC est conçu pour être utilisé comme outil de compilation, et tout bon système de compilation repose sur des sorties prévisibles pour chaque entrée. Cela signifie que chaque fichier .java doit produire 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, ces en-têtes ne parviennent pas à compiler:
../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 ~~~~~~~~~~~~~~ ^
Versions segmentées de Foo.h et Bar.h, qui se compileront 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")