@class vs. #import

709

É do meu entendimento que se deve usar uma declaração de classe de encaminhamento caso a Classe A precise incluir um cabeçalho da Classe B e a Classe B precise incluir um cabeçalho da Classe A para evitar inclusões circulares. Também entendo que an #importé simples, de ifndefmodo que uma inclusão acontece apenas uma vez.

Minha pergunta é a seguinte: quando alguém usa #importe quando alguém usa @class? Às vezes, se eu usar uma @classdeclaração, vejo um aviso comum do compilador, como o seguinte:

warning: receiver 'FooController' is a forward class and corresponding @interface may not exist.

Realmente adoraria entender isso, em vez de apenas remover a @classdeclaração direta e colocar um #importpara silenciar os avisos que o compilador está me dando.

Coocoo4Cocoa
fonte
10
A declaração de encaminhamento apenas informa ao compilador: "Ei, eu sei que estou declarando coisas que você não reconhece, mas quando digo @MyClass, prometo que vou #importá-lo na implementação".
JoeCortopassi

Respostas:

754

Se você vir este aviso:

aviso: o receptor 'MyCoolClass' é uma classe de encaminhamento e a @interface correspondente pode não existir

você precisa #importdo arquivo, mas pode fazer isso no seu arquivo de implementação (.m) e usar a @classdeclaração no seu arquivo de cabeçalho.

@class(geralmente) não remove a necessidade de #importarquivos, apenas move o requisito para mais perto de onde as informações são úteis.

Por exemplo

Se você diz @class MyCoolClass, o compilador sabe que pode ver algo como:

MyCoolClass *myObject;

Ele não precisa se preocupar com nada além de MyCoolClassuma classe válida e deve reservar espaço para um ponteiro para ela (realmente, apenas um ponteiro). Assim, em seu cabeçalho, @classbasta 90% do tempo.

No entanto, se você precisar criar ou acessar myObjectmembros, precisará informar ao compilador quais são esses métodos. Neste ponto (presumivelmente no seu arquivo de implementação), você precisará #import "MyCoolClass.h"informar ao compilador informações adicionais além de apenas "esta é uma classe".

Ben Gottlieb
fonte
5
Ótima resposta, obrigado. Para referência futura: isto também lida com situações em que @classalgo em seu .harquivo, mas se esqueça de #importque na .m, tentam acessar um método do @classobjeto ed, e obter avisos como: warning: no -X method found.
Tim
24
Um caso em que você precisaria #importar em vez de @class é se o arquivo .h inclui tipos de dados ou outras definições necessárias para a interface da sua classe.
Ken Aspeslagh
2
Outra grande vantagem não mencionada aqui é a compilação rápida. Por favor, consulte a resposta de Venkateshwar
MartinMoizard
@BenGottlieb Esse 'm' em "myCoolClass" não deveria estar em maiúsculas? Como em "MyCoolClass"?
Basil Bourque
182

Três regras simples:

  • Somente #importa superclasse e os protocolos adotados nos arquivos de cabeçalho ( .harquivos).
  • #importtodas as classes e protocolos para os quais você envia mensagens na implementação ( .marquivos).
  • Encaminhar declarações para todo o resto.

Se você encaminhar a declaração nos arquivos de implementação, provavelmente fará algo errado.

PeyloW
fonte
22
Nos arquivos de cabeçalho, você também pode # importar qualquer coisa que defina um protocolo adotado por sua classe.
Tyler
Existe uma diferença na declaração #import no arquivo de interface h ou no arquivo de implementação m?
Samuel G
E #import se você usar variáveis ​​de instância da classe
user151019
1
@ Mark - Coberto pela regra nº 1, acesse apenas ivars da sua superclasse, se for o caso.
PeyloW
@ Tyler, por que não encaminhar a declaração do protocolo?
JoeCortopassi
110

Veja a documentação da linguagem de programação Objective-C no ADC

Sob a seção Definindo uma classe | Interface de classe descreve por que isso é feito:

A diretiva @class minimiza a quantidade de código vista pelo compilador e vinculador e, portanto, é a maneira mais simples de fornecer uma declaração de encaminhamento de um nome de classe. Por ser simples, evita possíveis problemas que podem surgir com a importação de arquivos que importam ainda outros arquivos. Por exemplo, se uma classe declara uma variável de instância digitada estaticamente de outra classe e seus dois arquivos de interface se importam, nenhuma das classes pode compilar corretamente.

Eu espero que isso ajude.

Abizern
fonte
48

Use uma declaração de encaminhamento no arquivo de cabeçalho, se necessário, e #importos arquivos de cabeçalho para todas as classes que você estiver usando na implementação. Em outras palavras, você sempre #importos arquivos que está usando na sua implementação e, se precisar fazer referência a uma classe no arquivo de cabeçalho, use também uma declaração de encaminhamento.

A exceção é que você deve #importherdar uma classe ou protocolo formal do arquivo de cabeçalho (nesse caso, não seria necessário importá-lo na implementação).

Marc Charbonneau
fonte
24

A prática comum é usar @class nos arquivos de cabeçalho (mas você ainda precisa # importar a superclasse) e # import nos arquivos de implementação. Isso evitará inclusões circulares e simplesmente funcionará.

Steph Thirion
fonte
2
Eu pensei que #import era melhor que #Include, pois importa apenas uma instância?
Matthew Schinckel 27/11/2008
2
Verdade. Não sei se se trata de inclusões circulares ou de pedidos incorretos, mas evitei essa regra (com uma importação em um cabeçalho, as importações não eram mais necessárias na implementação da subclasse) e logo ficou muito confuso. Resumindo, siga essa regra e o compilador ficará feliz.
Steph Thirion
1
Os documentos atuais dizem que #import"é como a diretiva #include de C, exceto que garante que o mesmo arquivo nunca seja incluído mais de uma vez". Portanto, de acordo com isso, #importtrata-se de inclusões circulares, as @classdiretivas não ajudam particularmente nisso.
31413 Eric
24

Outra vantagem: Compilação rápida

Se você incluir um arquivo de cabeçalho, qualquer alteração nele fará com que o arquivo atual também seja compilado, mas esse não será o caso se o nome da classe for incluído como @class name. Claro que você precisará incluir o cabeçalho no arquivo de origem

desabafar
fonte
18

Minha pergunta é essa. Quando alguém usa #import e quando usa @class?

Resposta simples: você #importou #includequando há uma dependência física. Caso contrário, você pode usar declarações para a frente ( @class MONClass, struct MONStruct, @protocol MONProtocol).

Aqui estão alguns exemplos comuns de dependência física:

  • Qualquer valor C ou C ++ (um ponteiro ou referência não é uma dependência física). Se você possui um CGPointivar ou propriedade, o compilador precisará ver a declaração de CGPoint.
  • Sua superclasse.
  • Um método que você usa.

Às vezes, se eu usar uma declaração @class, vejo um aviso comum do compilador, como o seguinte: "warning: receiver 'FooController' é uma classe forward e @interface correspondente pode não existir".

O compilador é realmente muito indulgente a esse respeito. Ele soltará sugestões (como a acima), mas você pode lixeira sua pilha facilmente se ignorá-las e não fizer isso #importcorretamente. Embora deva (IMO), o compilador não impõe isso. No ARC, o compilador é mais rigoroso porque é responsável pela contagem de referências. O que acontece é que o compilador volta ao padrão quando encontra um método desconhecido que você chama. Todo valor e parâmetro de retorno é assumido como sendo id. Portanto, você deve erradicar todos os avisos de suas bases de código, pois isso deve ser considerado dependência física. Isso é análogo a chamar uma função C que não é declarada. Com C, os parâmetros são assumidos como sendo int.

O motivo de você favorecer as declarações de encaminhamento é que você pode reduzir o tempo de construção por fatores, porque há uma dependência mínima. Com as declarações de encaminhamento, o compilador vê que há um nome e pode analisar e compilar corretamente o programa sem ver a declaração de classe ou todas as suas dependências quando não houver dependência física. Construções limpas levam menos tempo. Construções incrementais levam menos tempo. Claro, você acabará gastando um pouco mais de tempo, garantindo que todos os cabeçalhos necessários sejam visíveis para todas as traduções como conseqüência, mas isso compensa em tempos de compilação reduzidos rapidamente (assumindo que seu projeto não seja pequeno).

Se você usar #importou #include, em vez disso, estará lançando muito mais trabalho no compilador do que o necessário. Você também está introduzindo dependências complexas de cabeçalho. Você pode comparar isso com um algoritmo de força bruta. Quando você #importestá arrastando toneladas de informações desnecessárias, o que requer muita memória, E / S de disco e CPU para analisar e compilar as fontes.

O ObjC é bem próximo do ideal para uma linguagem baseada em C no que diz respeito à dependência, porque os NSObjecttipos nunca são valores - os NSObjecttipos sempre são indicadores contados como referência. Assim, você poderá obter tempos de compilação incrivelmente rápidos se estruturar adequadamente as dependências do programa e avançar sempre que possível, porque é necessária uma dependência física muito pequena. Você também pode declarar propriedades nas extensões de classe para minimizar ainda mais a dependência. Esse é um grande bônus para sistemas grandes - você saberia a diferença que faz se já desenvolveu uma grande base de código C ++.

Portanto, minha recomendação é usar os encaminhadores sempre que possível e depois para #importonde houver dependência física. Se você vir o aviso ou outro que implica dependência física - corrija-os todos. A correção está #importno seu arquivo de implementação.

Ao criar bibliotecas, você provavelmente classificará algumas interfaces como um grupo; nesse caso, você classificaria #importa biblioteca em que a dependência física é introduzida (por exemplo #import <AppKit/AppKit.h>). Isso pode introduzir dependência, mas os mantenedores da biblioteca geralmente podem lidar com as dependências físicas conforme necessário - se eles introduzirem um recurso, poderão minimizar o impacto que ele tem nas suas construções.

justin
fonte
Entre bom esforço para explicar as coisas. .mas parecem ser bastante complexos.
precisa
NSObject types are never values -- NSObject types are always reference counted pointers.não é inteiramente verdade. Blocos jogam uma brecha na sua resposta, apenas dizendo.
Richard J. Ross III
@ RichardJ.RossIII… e o GCC permite declarar e usar valores, enquanto o clang o proíbe. e, é claro, deve haver um valor por trás do ponteiro.
justin
11

Eu vejo muito "Faça dessa maneira", mas não vejo respostas para "Por que?"

Então: por que você deveria @class no cabeçalho e #import apenas na sua implementação? Você está dobrando seu trabalho tendo que @class e #import o tempo todo. A menos que você faça uso da herança. Nesse caso, você estará #importando várias vezes para uma única @class. Lembre-se de remover de vários arquivos diferentes se você decidir de repente que não precisa mais acessar uma declaração.

Importar o mesmo arquivo várias vezes não é um problema devido à natureza de #import. Compilar o desempenho também não é realmente um problema. Se fosse, não estaríamos #importando Cocoa / Cocoa.h ou similares em praticamente todos os arquivos de cabeçalho que temos.

Bruce Goodwin
fonte
1
veja a resposta de Abizem acima para obter um exemplo da documentação de por que você deve fazer isso. É uma programação defensiva para quando você tem dois cabeçalhos de classe que se importam com variáveis ​​de instância da outra classe.
Jackslash #
7

se fizermos isso

@interface Class_B : Class_A

significa que estamos herdando a Class_A na Class_B, na Class_B podemos acessar todas as variáveis ​​da class_A.

se estamos fazendo isso

#import ....
@class Class_A
@interface Class_B

aqui estamos dizendo que estamos usando o Class_A em nosso programa, mas se quisermos usar as variáveis ​​Class_A no Class_B, precisaremos # importar o Class_A no arquivo .m (crie um objeto e use suas funções e variáveis).

Anshuman Mishra
fonte
5

para obter informações adicionais sobre dependências de arquivos & #import & @class, confira:

http://qualitycoding.org/file-dependencies/ é um bom artigo

resumo do artigo

importações em arquivos de cabeçalho:

  • #importe a superclasse que você está herdando e os protocolos que você está implementando.
  • Reencaminhar-declarar tudo o resto (a menos que provenha de uma estrutura com um cabeçalho mestre).
  • Tente eliminar todos os outros #imports.
  • Declarar protocolos em seus próprios cabeçalhos para reduzir dependências.
  • Muitas declarações avançadas? Você tem uma classe grande.

importações em arquivos de implementação:

  • Elimine as # importações de cruft que não são usadas.
  • Se um método delega para outro objeto e retorna o que recebe, tente declarar o objeto adiante em vez de #importá-lo.
  • Se a inclusão de um módulo obriga a incluir nível após nível de dependências sucessivas, você pode ter um conjunto de classes que deseja se tornar uma biblioteca. Construa-o como uma biblioteca separada com um cabeçalho mestre, para que tudo possa ser trazido como um único bloco pré-construído.
  • Muitas #importações? Você tem uma classe grande.
Homam
fonte
3

Quando me desenvolvo, tenho apenas três coisas em mente que nunca me causam problemas.

  1. Importar super classes
  2. Importar classes pai (quando você tem filhos e pais)
  3. Importar classes para fora do seu projeto (como em estruturas e bibliotecas)

Para todas as outras classes (subclasses e classes filho no meu próprio projeto), eu as declaro por meio de classe direta.

Constantino Tsarouhas
fonte
3

Se você tentar declarar uma variável ou propriedade no seu arquivo de cabeçalho, que ainda não importou, você receberá um erro dizendo que o compilador não conhece essa classe.

Seu primeiro pensamento é provavelmente #importisso.
Isso pode causar problemas em alguns casos.

Por exemplo, se você implementar vários métodos C no arquivo de cabeçalho, ou estruturas, ou algo semelhante, porque eles não devem ser importados várias vezes.

Portanto, você pode informar ao compilador com @class:

Eu sei que você não conhece essa classe, mas ela existe. Será importado ou implementado em outro lugar

Basicamente, ele diz ao compilador para desligar e compilar, mesmo que não tenha certeza se essa classe será implementada.

Normalmente você vai usar #importna .m e @classnas .h arquivos.

IluTov
fonte
0

Encaminhar a declaração apenas para impedir que o compilador mostre erro.

o compilador saberá que há uma classe com o nome que você usou no arquivo de cabeçalho para declarar.

Karthick
fonte
você poderia ser um pouco mais especifico?
Sam Spencer
0

O compilador reclamará apenas se você for usar essa classe de forma que o compilador precise conhecer sua implementação.

Ex:

  1. Pode ser como se você derivasse sua classe disso ou
  2. Se você quiser ter um objeto dessa classe como uma variável de membro (embora raro).

Não vai reclamar se você apenas vai usá-lo como um ponteiro. Obviamente, você precisará # importá-lo no arquivo de implementação (se estiver instanciando um objeto dessa classe), pois ele precisa conhecer o conteúdo da classe para instanciar um objeto.

NOTA: #import não é o mesmo que #include. Isso significa que não há nada chamado importação circular. A importação é um tipo de solicitação para o compilador procurar um arquivo específico para obter algumas informações. Se essa informação já estiver disponível, o compilador a ignora.

Apenas tente isso, importe Ah em Bh e Bh em Ah Não haverá problemas ou reclamações e também funcionará bem.

Quando usar @class

Você usa @class apenas se não quiser importar um cabeçalho no seu cabeçalho. Pode ser um caso em que você nem se importa em saber qual será essa aula. Casos em que você ainda não pode ter um cabeçalho para essa classe.

Um exemplo disso pode ser o fato de você estar escrevendo duas bibliotecas. Uma classe, vamos chamá-la de A, existe em uma biblioteca. Esta biblioteca inclui um cabeçalho da segunda biblioteca. Esse cabeçalho pode ter um ponteiro de A, mas novamente pode não ser necessário usá-lo. Se a biblioteca 1 ainda não estiver disponível, a biblioteca B não será bloqueada se você usar @class. Mas se você deseja importar Ah, o progresso da biblioteca 2 é bloqueado.

Deepak GM
fonte
0

Pense no @class como dizendo ao compilador "confie em mim, isso existe".

Pense em #import como copiar e colar.

Você deseja minimizar o número de importações que você possui por vários motivos. Sem nenhuma pesquisa, a primeira coisa que vem à mente é que reduz o tempo de compilação.

Observe que, quando você herda de uma classe, não pode simplesmente usar uma declaração direta. Você precisa importar o arquivo, para que a classe que você está declarando saiba como é definida.

Brandon M
fonte
0

Este é um cenário de exemplo, onde precisamos de @class.

Considere se você deseja criar um protocolo no arquivo de cabeçalho, que possui um parâmetro com o tipo de dados da mesma classe, então você pode usar @class. Lembre-se de que você também pode declarar protocolos separadamente, este é apenas um exemplo.

// DroneSearchField.h

#import <UIKit/UIKit.h>
@class DroneSearchField;
@protocol DroneSearchFieldDelegate<UITextFieldDelegate>
@optional
- (void)DroneTextFieldButtonClicked:(DroneSearchField *)textField;
@end
@interface DroneSearchField : UITextField
@end
Sujananth
fonte