Intestazioni segmentate

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

Cosa devi sapere

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

Inclusioni circolari

I file di intestazione generati da J2ObjC devono utilizzare dichiarazioni di inclusione e inoltro per risolvere le informazioni sul tipo necessarie. Le dichiarazioni di inoltro vengono utilizzate il più possibile, ma le inclusioni sono necessarie per i tipi che vengono 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, richiediamo una classe nel file A che estenda una classe nel file B e una classe nel file B che estendi una classe nel file A. Si tratta di uno scenario improbabile, ma si verifica nel codebase di Guava (e altrove).

Una soluzione naturale per questo problema potrebbe essere emettere un file di intestazione separato per ogni tipo Java rilevato in un file .java. Tuttavia, J2ObjC è progettato per essere utilizzato come strumento di creazione e ogni buon sistema di build 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_

Nota che Foo.h include Bar.h e Bar.h include Foo.h. Di conseguenza, la compilazione di queste intestazioni non riesce:

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

Di seguito sono riportate le 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")