Você pode me orientar sobre como vincular corretamente a biblioteca estática ao projeto do iPhone. Eu uso o projeto de biblioteca estática adicionado ao projeto do aplicativo como dependência direta (destino -> geral -> dependências diretas) e tudo funciona bem, mas categorias. Uma categoria definida na biblioteca estática não está funcionando no aplicativo.
Então, minha pergunta é como adicionar uma biblioteca estática com algumas categorias em outro projeto?
E, em geral, qual é a melhor prática a ser usada no código de projeto de aplicativo de outros projetos?
iphone
objective-c
static-libraries
categories
Vladimir
fonte
fonte
Respostas:
Solução: A partir do Xcode 4.2, você só precisa acessar o aplicativo vinculado à biblioteca (não a biblioteca) e clicar no projeto no Navegador de Projeto, clicar no destino do aplicativo, criar configurações e procurar "Outros Sinalizadores de vinculador ", clique no botão + e adicione '-ObjC'. '-all_load' e '-force_load' não são mais necessários.
Detalhes: Encontrei algumas respostas em vários fóruns, blogs e documentos da apple. Agora, tento fazer um breve resumo de minhas pesquisas e experiências.
O problema foi causado por (citação de Perguntas e respostas técnicas da Apple QA1490 https://developer.apple.com/library/content/qa/qa1490/_index.html ):
E a solução deles:
e também há recomendações nas Perguntas frequentes sobre desenvolvimento do iPhone:
e descrições de bandeiras:
* podemos usar o force_load para reduzir o tamanho binário do aplicativo e evitar conflitos que o all_load pode causar em alguns casos.
Sim, funciona com arquivos * .a adicionados ao projeto. No entanto, tive problemas com o projeto lib adicionado como dependência direta. Mas depois descobri que era minha culpa - o projeto de dependência direta possivelmente não foi adicionado adequadamente. Quando removo e adiciono novamente com as etapas:
depois disso tudo funciona bem. A flag "-ObjC" foi suficiente no meu caso.
Eu também estava interessado em ideias do blog http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.html . O autor diz que pode usar a categoria da lib sem definir o sinalizador -all_load ou -ObjC. Ele acabou de adicionar à categoria h / m arquivos interface / implementação de classe fictícia vazia para forçar o vinculador a usar esse arquivo. E sim, esse truque faz o trabalho.
Mas o autor também disse que nem instanciava objetos fictícios. Mm ... Como eu descobri, devemos chamar explicitamente algum código "real" do arquivo de categoria. Portanto, pelo menos a função de classe deve ser chamada. E nós nem precisamos de aula fictícia. A função c simples faz o mesmo.
Portanto, se escrevermos arquivos lib como:
e se chamamos useMyLib (); em qualquer lugar no projeto App, em qualquer classe, podemos usar o método da categoria logSelf;
E mais blogs sobre o tema:
http://t-machine.org/index.php/2009/10/13/how-to-make-an-iphone-static-library-part-1/
http://blog.costan.us/2009/12/fat-iphone-static-libraries-device-and.html
fonte
A resposta de Vladimir é realmente muito boa, no entanto, gostaria de dar um pouco mais de conhecimento aqui. Talvez um dia alguém encontre minha resposta e possa ser útil.
O compilador transforma os arquivos de origem (.c, .cc, .cpp, .m) em arquivos de objeto (.o). Há um arquivo de objeto por arquivo de origem. Os arquivos de objetos contêm símbolos, código e dados. Os arquivos de objeto não podem ser usados diretamente pelo sistema operacional.
Agora, ao criar uma biblioteca dinâmica (.dylib), uma estrutura, um pacote configurável carregável (.bundle) ou um binário executável, esses arquivos de objeto são vinculados pelo vinculador para produzir algo que o sistema operacional considera "utilizável", por exemplo, algo que pode carregar diretamente em um endereço de memória específico.
No entanto, ao criar uma biblioteca estática, todos esses arquivos de objeto são simplesmente adicionados a um grande arquivo morto, daí a extensão de bibliotecas estáticas (.a para arquivo morto). Portanto, um arquivo .a nada mais é do que um arquivo morto de objetos (.o). Pense em um arquivo TAR ou em um arquivo ZIP sem compactação. É mais fácil copiar um único arquivo .a do que um monte de arquivos .o (semelhante ao Java, onde você agrupa arquivos .class em um arquivo .jar para facilitar a distribuição).
Ao vincular um binário a uma biblioteca estática (= arquivo morto), o vinculador obtém uma tabela de todos os símbolos no arquivo morto e verifica quais desses símbolos são referenciados pelos binários. Somente os arquivos de objeto que contêm símbolos referenciados são realmente carregados pelo vinculador e são considerados pelo processo de vinculação. Por exemplo, se o seu arquivo possui 50 arquivos de objeto, mas apenas 20 contêm símbolos usados pelo binário, apenas os 20 são carregados pelo vinculador, os outros 30 são totalmente ignorados no processo de vinculação.
Isso funciona muito bem para os códigos C e C ++, pois essas linguagens tentam fazer o máximo possível em tempo de compilação (embora o C ++ também tenha alguns recursos apenas de tempo de execução). Obj-C, no entanto, é um tipo diferente de linguagem. O Obj-C depende muito dos recursos de tempo de execução e muitos recursos de Obj-C são, na verdade, somente de tempo de execução. As classes Obj-C realmente têm símbolos comparáveis às funções C ou variáveis C globais (pelo menos no tempo de execução atual do Obj-C). Um vinculador pode ver se uma classe é referenciada ou não, para determinar se uma classe está sendo usada ou não. Se você usar uma classe de um arquivo de objeto em uma biblioteca estática, esse arquivo de objeto será carregado pelo vinculador porque o vinculador vê um símbolo em uso. Categorias são um recurso apenas de tempo de execução, categorias não são símbolos como classes ou funções e isso também significa que um vinculador não pode determinar se uma categoria está em uso ou não.
Se o vinculador carregar um arquivo de objeto contendo o código Obj-C, todas as partes dele serão sempre parte do estágio de vinculação. Portanto, se um arquivo de objeto contendo categorias for carregado porque qualquer símbolo dele é considerado "em uso" (seja uma classe, seja uma função, seja uma variável global), as categorias também serão carregadas e estarão disponíveis no tempo de execução . No entanto, se o próprio arquivo de objeto não for carregado, as categorias nele não estarão disponíveis no tempo de execução. Um arquivo de objeto contendo apenas categorias nunca é carregado porque não contém símbolos que o vinculador jamais considerar "em uso". E este é todo o problema aqui.
Várias soluções foram propostas e agora que você sabe como tudo isso funciona em conjunto, vamos dar uma olhada na solução proposta:
Uma solução é adicionar
-all_load
à chamada do vinculador. O que esse sinalizador de vinculador realmente fará? Na verdade, ele diz ao vinculador o seguinte " Carregue todos os arquivos de objeto de todos os arquivos, independentemente de você ver ou não algum símbolo '. É claro que isso funcionará; mas também poderá produzir binários bastante grandes.Outra solução é adicionar
-force_load
à chamada do vinculador, incluindo o caminho para o arquivo morto. Esse sinalizador funciona exatamente como-all_load
, mas apenas para o arquivo especificado. Claro que isso também funcionará.A solução mais popular é adicionar
-ObjC
à chamada do vinculador. O que esse sinalizador de vinculador realmente fará? Esse sinalizador informa ao vinculador " Carregue todos os arquivos de objetos de todos os arquivos, se você vir que eles contêm algum código Obj-C ". E "qualquer código Obj-C" inclui categorias. Isso também funcionará e não forçará o carregamento de arquivos de objetos que não contêm código Obj-C (eles ainda são carregados somente sob demanda).Outra solução é a nova configuração de compilação do Xcode
Perform Single-Object Prelink
. O que essa configuração fará? Se ativado, todos os arquivos de objeto (lembre-se, existe um por arquivo de origem) são mesclados em um único arquivo de objeto (que não é um vínculo real, daí o nome PreLink ) e esse arquivo de objeto único (às vezes também chamado de "objeto mestre" arquivo ") é então adicionado ao arquivo. Se agora qualquer símbolo do arquivo de objeto principal for considerado em uso, todo o arquivo de objeto principal será considerado em uso e, portanto, todas as partes do Objective-C dele serão sempre carregadas. E como as classes são símbolos normais, basta usar uma classe de uma biblioteca estática para obter também todas as categorias.A solução final é o truque que Vladimir adicionou no final de sua resposta. Coloque um " símbolo falso " em qualquer arquivo de origem, declarando apenas categorias. Se você deseja usar qualquer uma das categorias no tempo de execução, certifique-se de alguma forma referenciar o símbolo falso no tempo de compilação, pois isso faz com que o arquivo de objeto seja carregado pelo vinculador e, portanto, também todo o código Obj-C nele. Por exemplo, pode ser uma função com um corpo de função vazio (que não fará nada ao ser chamado) ou pode ser uma variável global acessada (por exemplo, uma variável global
int
uma vez lido ou escrito, isso é suficiente). Diferente de todas as outras soluções acima, essa solução muda o controle sobre quais categorias estão disponíveis em tempo de execução para o código compilado (se desejar que elas estejam vinculadas e disponíveis, acessa o símbolo, caso contrário, não acessa o símbolo e o vinculador ignorará isto).Isso é tudo, pessoal.
Ah, espere, há mais uma coisa:
o vinculador tem uma opção chamada
-dead_strip
. O que essa opção faz? Se o vinculador decidir carregar um arquivo de objeto, todos os símbolos do arquivo de objeto se tornarão parte do binário vinculado, sejam eles usados ou não. Por exemplo, um arquivo de objeto contém 100 funções, mas apenas uma delas é usada pelo binário, todas as 100 funções ainda são adicionadas ao binário porque os arquivos de objeto são adicionados como um todo ou não são adicionados. A adição parcial de um arquivo de objeto geralmente não é suportada por vinculadores.No entanto, se você instruir o vinculador a "faixa inoperante", o vinculador primeiro adicionará todos os arquivos de objeto ao binário, resolverá todas as referências e finalmente examinará o binário em busca de símbolos que não estejam em uso (ou apenas em uso por outros símbolos que não estejam em uso). usar). Todos os símbolos que não estão em uso são removidos como parte do estágio de otimização. No exemplo acima, as 99 funções não utilizadas são removidas novamente. Isso é muito útil se você usar opções como
-load_all
,-force_load
ouPerform Single-Object Prelink
porque essas opções podem facilmente explodir tamanhos binários drasticamente em alguns casos, e a remoção morta removerá código e dados não utilizados novamente.A remoção morta funciona muito bem para o código C (por exemplo, funções, variáveis e constantes não utilizadas são removidas conforme o esperado) e também funciona muito bem para C ++ (por exemplo, as classes não utilizadas são removidas). Não é perfeito; em alguns casos, alguns símbolos não são removidos, mesmo que seja bom removê-los, mas na maioria dos casos funciona muito bem para esses idiomas.
E o Obj-C? Esqueça isso! Não há remoção morta para Obj-C. Como o Obj-C é uma linguagem de recurso de tempo de execução, o compilador não pode dizer em tempo de compilação se um símbolo está realmente em uso ou não. Por exemplo, uma classe Obj-C não está em uso se não houver código que faça referência direta a ela, correto? Errado! Você pode criar dinamicamente uma sequência contendo um nome de classe, solicitar um ponteiro de classe para esse nome e alocar dinamicamente a classe. Por exemplo, em vez de
Eu também poderia escrever
Nos dois casos,
mmc
há uma referência a um objeto da classe "MyCoolClass", mas não há referência direta a essa classe no segundo exemplo de código (nem mesmo o nome da classe como uma sequência estática). Tudo acontece apenas em tempo de execução. E isso mesmo que as classes sejam realmente símbolos reais. É ainda pior para as categorias, pois elas nem são símbolos reais.Portanto, se você tem uma biblioteca estática com centenas de objetos, mas a maioria dos seus binários precisa apenas de alguns deles, talvez prefira não usar as soluções (1) a (4) acima. Caso contrário, você terminará com binários muito grandes contendo todas essas classes, mesmo que a maioria nunca seja usada. Para as classes, geralmente você não precisa de nenhuma solução especial, uma vez que as classes têm símbolos reais e, desde que você as refira diretamente (não como no segundo exemplo de código), o vinculador identificará seu uso por si só. Para categorias, no entanto, considere a solução (5), pois torna possível incluir apenas as categorias que você realmente precisa.
Por exemplo, se você deseja uma categoria para o NSData, por exemplo, adicionando um método de compactação / descompactação, você cria um arquivo de cabeçalho:
e um arquivo de implementação
Agora apenas certifique-se de que qualquer lugar no seu código
import_NSData_Compression()
seja chamado. Não importa onde é chamado ou com que frequência é chamado. Na verdade, ele realmente não precisa ser chamado, basta que o vinculador pense assim. Por exemplo, você pode colocar o seguinte código em qualquer lugar do seu projeto:Você nunca precisa chamar
importCategories()
seu código, o atributo fará o compilador e o vinculador acreditarem que ele é chamado, mesmo que não seja.E uma dica final:
se você adicionar
-whyload
à chamada de link final, o vinculador imprimirá no log de compilação qual arquivo de objeto de qual biblioteca foi carregada devido a qual símbolo em uso. Ele imprimirá apenas o primeiro símbolo considerado em uso, mas esse não é necessariamente o único símbolo em uso desse arquivo de objeto.fonte
-whyload
, tentar depurar por que o vinculador está fazendo algo pode ser bastante difícil!Dead Code Stripping
noBuild Settings>Linking
. É o mesmo que-dead_strip
adicionadoOther Linker Flags
?-ObjC
, então tentei o seu hack, mas ele reclama"import_NSString_jsonObject()", referenced from: importCategories() in main.o ld: symbol(s) not found
. Eu coloqueiimport_NSString_jsonObject
no meu Framework incorporado chamadoUtility
e adicionei#import <Utility/Utility.h>
uma__attribute__
declaração no final do meuAppDelegate.h
.Este problema foi corrigido no LLVM . A correção é enviada como parte do LLVM 2.9 A primeira versão do Xcode a conter a correção é o Xcode 4.2 enviado com o LLVM 3.0. O uso
-all_load
ou-force_load
não é mais necessário ao trabalhar com o XCode 4.2-ObjC
ainda é necessário.fonte
-ObjC
bandeira ainda é necessária e sempre será. A solução alternativa foi o uso de-all_load
ou-force_load
. E isso não é mais necessário. Corrigi minha resposta acima.Aqui está o que você precisa fazer para resolver esse problema completamente ao compilar sua biblioteca estática:
Vá para Configurações de compilação do Xcode e defina Executar pré-vinculação de objeto único como YES ou
GENERATE_MASTER_OBJECT_FILE = YES
no arquivo de configuração da compilação.Por padrão, o vinculador gera um arquivo .o para cada arquivo .m. Portanto, as categorias obtêm arquivos .o diferentes. Quando o vinculador examina os arquivos .o da biblioteca estática, ele não cria um índice de todos os símbolos por classe (o Runtime fará, não importa o quê).
Essa diretiva solicitará que o vinculador empacote todos os objetos em um grande arquivo .o e, por isso, força o vinculador que processa a biblioteca estática a obter o índice de todas as categorias de classe.
Espero que isso esclareça.
fonte
Um fator que raramente é mencionado sempre que a discussão sobre a vinculação da biblioteca estática é o fato de que você também deve incluir as categorias nas fases de construção-> copiar arquivos e compilar fontes da própria biblioteca estática .
A Apple também não enfatiza esse fato em suas recentemente usadas Using Static Libraries in iOS .
Passei um dia inteiro tentando todos os tipos de variações de -objC e -all_load etc. Mas nada saiu disso. Essa questão chamou minha atenção para esse problema. (não me interpretem mal ... você ainda precisa fazer as coisas -objC .. mas é mais do que isso).
Outra ação que sempre me ajudou é que eu sempre construo a biblioteca estática incluída primeiro por conta própria .. então eu construo o aplicativo anexo ..
fonte
Você provavelmente precisa ter a categoria no cabeçalho "público" da biblioteca estática: #import "MyStaticLib.h"
fonte