Cabeçalhos segmentados

Os arquivos de cabeçalhos gerados pelo J2ObjC são divididos em segmentos e podem ser incluídos um de cada vez. Um segmento é criado para cada tipo Java traduzido. Assim, cada classe interna terá o próprio segmento. Macros de pré-processador são usadas para informar ao compilador que ele deve ler apenas um segmento específico ao incluir o cabeçalho. Os cabeçalhos segmentados resolvem o problema de ciclos de inclusão em cabeçalhos gerados pelo J2ObjC, descritos em detalhes neste documento.

O que você precisa saber

  • Use #include em vez de #import para incluir cabeçalhos gerados pelo J2ObjC.
    • O uso de #import com cabeçalhos segmentados é problemático porque o compilador pula a leitura do cabeçalho se ele já tiver sido lido. No entanto, como o cabeçalho está segmentado, ele pode não ter sido totalmente analisado pelo compilador na primeira vez.
  • Os cabeçalhos segmentados podem ser desativados com a flag --no-segmented-headers.

Circular inclui

Os arquivos de cabeçalho gerados pelo J2ObjC precisam usar declarações de inclusão e de encaminhamento para resolver as informações de tipo necessárias. As declarações antecipadas são usadas o máximo possível, mas as inclusões são necessárias para tipos estendidos ou implementados porque o compilador exige a declaração completa do tipo.

É possível gerar ciclos de inclusão em arquivos de cabeçalho gerados pelo J2ObjC. Para criar esse ciclo, precisamos de uma classe no arquivo A que estenda uma classe no arquivo B e uma classe no arquivo B que estenda uma classe no arquivo A. Esse é um cenário improvável, mas ele ocorre na base de código do Guava (e em outros lugares).

Uma correção natural para esse problema pode ser emitir um arquivo de cabeçalho separado para cada tipo Java encontrado em um arquivo .java. No entanto, o J2ObjC foi projetado para ser usado como uma ferramenta de build, e qualquer sistema de build bom depende de saídas previsíveis para cada entrada. Isso significa que cada arquivo .java precisa produzir exatamente um arquivo .h e um arquivo .m.

Exemplo

Foo.java:

class Foo extends Bar {}

Bar.java:

class Bar {
  static class Baz extends Foo {}
}

Foo.h (não segmentado):

#ifndef _Foo_H_
#define _Foo_H_

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

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

#endif // _Foo_H_

Bar.h (não segmentado):

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

Observe que Foo.h inclui Bar.h e Bar.h inclui Foo.h. Como resultado, esses cabeçalhos não são compilados:

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

Versões segmentadas de Foo.h e Bar.h, que serão compiladas sem erros:

Foo.h (segmentado):

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

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