Intestazioni segmentate

I file di intestazioni generati da J2ObjC sono suddivisi in segmenti e possono essere inclusi uno alla volta. Viene creato un segmento per ogni tipo Java tradotto, quindi ogni classe interna avrà il proprio segmento. Le macro del preprocessore vengono utilizzate per indicare al compilatore di leggere solo un determinato segmento quando include l'intestazione. Le intestazioni segmentate risolvono il problema dei cicli di inclusione nelle intestazioni generate da J2ObjC, descritto in dettaglio in questo documento.

Informazioni importanti

  • Utilizza #include anziché #import per includere le intestazioni generate da J2ObjC.
    • L'utilizzo di #import con intestazioni segmentate è problematico perché il compilatore salterà la lettura dell'intestazione se l'ha già vista. Tuttavia, poiché l'header è segmentato, potrebbe non essere stato analizzato completamente dal compilatore la prima volta.
  • Le intestazioni segmentate possono essere disattivate con il flag --no-segmented-headers.

Contenuti del circular

I file di intestazione generati da J2ObjC devono utilizzare include e dichiarazioni di inoltro per risolvere le informazioni sui tipi necessarie. Le dichiarazioni anticipate vengono utilizzate il più possibile, tuttavia gli include sono necessari per i tipi estesi o implementati perché il compilatore richiede la dichiarazione completa del tipo.

È possibile generare cicli di inclusione nei file di intestazione generati da J2ObjC. Per ottenere un ciclo di questo tipo, è necessario un corso nel file A che estenda una classe nel file B e un corso nel file B che estenda una classe nel file A. Si tratta di uno scenario improbabile, ma si verifica nel codice di Guava (e altrove).

Una soluzione naturale a questo problema potrebbe essere quella di generare un file di intestazione separato per ogni tipo Java incontrato in un file .java. Tuttavia, J2ObjC è progettato per essere utilizzato come strumento di compilazione e qualsiasi buon sistema di compilazione si basa su output prevedibili per ogni input. Ciò significa che è necessario che ogni file .java produca esattamente un file .h e un file .m.

Esempio

Foo.java:

class Foo extends Bar {}

Bar.java:

class Bar {
  static class Baz extends Foo {}
}

Foo.h (non segmentato):

#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 segmentato):

#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_

Tieni presente che Foo.h include Bar.h e Bar.h include Foo.h. Di conseguenza, la compilazione di questi header non va a buon fine:

../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
~~~~~~~~~~~~~~   ^

Versioni segmentate di Foo.h e Bar.h, che verranno compilate senza errori:

Foo.h (segmentato):

#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 (segmentato):

#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")