Como exportar o Cocoa Touch Framework “fat” (para Simulador e Dispositivo)?

107

Com o Xcode 6, temos a capacidade de criar o próprio Dynamic Cocoa Frameworks.

insira a descrição da imagem aqui

Por causa de:

  • Simulador ainda usa 32-bitbiblioteca

  • a partir de 1º de junho de 2015, as atualizações de aplicativos enviadas para a App Store devem incluir suporte de 64 bits e ser desenvolvidas com o iOS 8 SDK ( developer.apple.com )

Temos que fazer uma biblioteca de gordura para rodar projetos em dispositivos e simuladores. ou seja, suporta 32 e 64 bits em Frameworks.

Mas eu não encontrei nenhum manual de como exportar o Fat Framework universal para integração futura com outros projetos (e compartilhar esta biblioteca com alguém).

Aqui estão meus passos para reproduzir:

  1. Situado ONLY_ACTIVE_ARCH=NOnoBuild Settings

    insira a descrição da imagem aqui

  2. Adicionar suporte armv7 armv7s arm64 i386 x86_64para Architectures(com certeza)

insira a descrição da imagem aqui

  1. Construa o Framework e abra-o no Finder:

insira a descrição da imagem aqui insira a descrição da imagem aqui

  1. Adicione esta estrutura a outro projeto

Resultado atual:

Mas no final eu ainda tenho problemas em executar projeto com este framework em dispositivos e simulador de uma vez.

  • se eu pegar o framework da Debug-iphoneospasta - ele funciona em dispositivos e obtém erros em simuladores:ld: symbol(s) not found for architecture i386

      xcrun lipo -info CoreActionSheetPicker

    As arquiteturas no arquivo fat: CoreActionSheetPicker são: armv7 armv7s arm64

  • se eu pegar o quadro da Debug-iphonesimulatorpasta - funciona em simuladores. e eu tenho um erro no dispositivo:ld: symbol(s) not found for architecture arm64

      xcrun lipo -info CoreActionSheetPicker

    As arquiteturas no arquivo fat: CoreActionSheetPicker são: i386 x86_64

Então, como criar um framework dinâmico que funcione em dispositivos e simuladores?

Esta resposta está relacionada ao Xcode 6 iOS Criando um Cocoa Touch Framework - problemas de arquitetura, mas não é duplicado.


Atualizar:

Eu encontrei um "hack sujo" para este caso. Veja minha resposta abaixo . Se alguém souber de uma maneira mais conveniente - por favor, me avise!

skywinder
fonte
problema duplicado stackoverflow.com/questions/24039470/…
Andrius Steponavičius
@ AndriusSteponavičius esta pergunta foi feita 2 meses antes.
skywinder 02 de
Sim, mas há respostas muito mais detalhadas lá, que eu acho que os usuários devem saber
Andrius Steponavičius
Definir ONLY_ACTIVE_ARCH = NO nas configurações de compilação é uma etapa importante.
Jedidja
seu framework precisa de ambas as fatias i386 x86_64 no binário fat se você quiser executá-lo no simulador MESMO SE SEU COMPUTADOR TIVER UMA ARQUITETURA DE 64 BITS !!! Aprendi isso da maneira mais difícil.
J.beenie

Respostas:

82

A realidade desta resposta é: julho de 2015. É mais provável que as coisas mudem.

TLDR;

Atualmente o Xcode não possui ferramentas para exportação automática de framework fat universal, então o desenvolvedor deve recorrer ao uso manual da lipoferramenta. Ainda de acordo com este radar, antes do envio ao desenvolvedor da AppStore, que é o consumidor do framework, também deve ser usado lipopara retirar fatias do simulador de um framework.

Segue-se uma resposta mais longa


Fiz uma pesquisa semelhante no tópico (o link na parte inferior da resposta).

Eu não tinha encontrado qualquer documentação oficial sobre distribuição de então minha pesquisa foi baseada na exploração da Apple Developer Forum, projetos de Carthage e Realm e minhas próprias experiências com xcodebuild, lipo, codesignferramentas.

Aqui está uma longa citação (com um pouco de marcação minha) do tópico do Apple Developer Forums Exporting app with embedded framework :

Qual é a maneira correta de exportar um framework de um projeto de framework?

Atualmente, a única maneira é exatamente o que você fez:

  • Construa o alvo para o simulador e o dispositivo iOS.
  • Navegue até a pasta DerivedData do Xcode para esse projeto e lipo os dois binários juntos em uma única estrutura. No entanto, ao construir o destino da estrutura no Xcode, certifique-se de ajustar a configuração de destino 'Build Active Architecture Only' para 'NO'. Isso permitirá que o Xcode construa o destino para vários tipos binarty (arm64, armv7, etc). É por isso que funciona a partir do Xcode, mas não como um binário autônomo.

  • Além disso, você vai querer ter certeza de que o esquema está definido para uma versão de versão e construir o destino da estrutura contra a versão. Se você ainda estiver obtendo um erro de biblioteca não carregada, verifique as fatias de código na estrutura.

  • Use lipo -info MyFramworkBinarye examine o resultado.

lipo -info MyFrameworkBinary

O resultado é i386 x86_64 armv7 arm64

  • Estruturas universais modernas incluirão 4 fatias, mas podem incluir mais: i386 x86_64 armv7 arm64 Se você não vir pelo menos 4, pode ser por causa da configuração Build Active Architecture.

Isso descreve o processo da mesma forma que @skywinder o fez em sua resposta.

É assim que Carthage usa lipo e Realm usa lipo .


DETALHE IMPORTANTE

Há radar: Xcode 6.1.1 e 6.2: frameworks iOS contendo fatias do simulador não podem ser enviados para a App Store e uma longa discussão sobre isso no Realm # 1163 e Carthage # 188 que terminou em uma solução alternativa especial:

antes do envio à AppStore, os binários da estrutura do iOS devem ser retirados das fatias do simulador

Carthage tem um código especial: CopyFrameworks e peça de documentação correspondente:

Este script contorna um bug de envio da App Store acionado por binários universais.

O Realm tem um script especial: strip-frameworks.sh e peça de documentação correspondente:

Esta etapa é necessária para contornar um bug de envio da App Store ao arquivar binários universais.

Também há um bom artigo: Removendo arquiteturas indesejadas de bibliotecas dinâmicas no Xcode .

Eu mesmo usei o Realm's, strip-frameworks.shque funcionou perfeitamente para mim, sem nenhuma modificação, embora, claro, qualquer pessoa seja livre para escrever um do zero.


O link para o meu tópico que recomendo ler porque contém outro aspecto desta questão: assinatura de código - Criando frameworks iOS / OSX: é necessário assiná-los antes de distribuí-los para outros desenvolvedores?

Stanislav Pankevich
fonte
1
Eu usei lipo, mas quando o framework está construindo no simulador, ele mostra o identificador não resolvido com o nome da classe, mas no dispositivo funciona. Se usar a versão do simulador do binário, então está funcionando .. alguma ideia?
Susim Samanta
2
Não encontrei nenhuma evidência de que isso mudou pelo Xcode 8.2 em dezembro de 2016.: /
Geoffrey Wiseman
1
@Geoffrey, isso mudou no Xcode 9.2 ou algo diferente? Esta é minha primeira vez criando um framework binário para distribuição, e já estou com medo ...
ScottyB
Não faço isso há um tempo, infelizmente - não posso dizer. Boa sorte.
Geoffrey Wiseman
57

Esta não é uma solução tão clara, mas só há uma maneira que eu encontro:

  1. Situado ONLY_ACTIVE_ARCH=NOnoBuild Settings

    • Construir biblioteca para simulador
    • Biblioteca de construção para dispositivo
  2. Abra na Productspasta do console para o seu framework (você pode abri-lo abrindo a pasta do framework e cd ..de lá)

insira a descrição da imagem aqui insira a descrição da imagem aqui

  1. Execute este script da Productspasta. Ele cria o Fat Framework nesta pasta. (ou faça-o manualmente conforme explicado abaixo em 3. 4. )

Ou:

  1. Combine esses 2 Frameworks usando lipo por este script (substitua YourFrameworkNamepelo nome do seu Framework)

    lipo -create -output "YourFrameworkName" "Debug-iphonesimulator/YourFrameworkName.framework/YourFrameworkName" "Debug-iphoneos/YourFrameworkName.framework/YourFrameworkName"
  2. Substitua por um novo binário das estruturas existentes:

    cp -R Debug-iphoneos/YourFrameworkName.framework ./YourFrameworkName.framework
    mv YourFrameworkName ./YourFrameworkName.framework/YourFrameworkName

  1. Lucro: ./YourFrameworkName.framework- é um binário gordo pronto para usar! Você pode importá-lo para o seu projeto!

Para o projeto, que não está nos espaços de trabalho:

Você também pode tentar usar essa essência conforme descrito aqui . Mas parece que não funciona para projetos em espaços de trabalho.

skywinder
fonte
Achei que a Apple não aceita mais binários gordos. kodmunki.wordpress.com/2015/03/04/…
Monstieur
1
@skywinder Você encontrou alguma outra maneira simples de exportar o Cocoa Touch Framework para um binário fat pronto para usar? Estou usando a mesma abordagem acima, mas não gosto disso. O Xcode deve ter algum que automatize o processo.
dev gr
1
@devgr ainda não .. é por isso que não aceitei minha própria resposta. Ainda procurando uma solução melhor.
skywinder
1
Não é possível executar no simulador, mas funciona no dispositivo com as 3 e 4 etapas
jose920405
1
@ Alguém poderia explicar por que apenas a Debug-pasta é usada com lipo -create? Esta estrutura pode ser usada para Releaseconfiguração e por quê? Obrigado.
Yevhen Dubinin
10

A resposta de @Stainlav foi muito útil, mas o que eu fiz em vez disso foi compilar duas versões do framework (uma para o dispositivo e outra para o simulador) e depois adicionei o seguinte Run Script Phasepara copiar automaticamente o framework pré-compilado necessário para a arquitetura em execução

echo "Copying frameworks for architecture: $CURRENT_ARCH"
if [ "${CURRENT_ARCH}" = "x86_64" ] || [ "${CURRENT_ARCH}" = "i386" ]; then
  cp -af "${SRCROOT}/Frameworks/Simulator/." "${SRCROOT}/Frameworks/Active"
else
  cp -af "${SRCROOT}/Frameworks/Device/." "${SRCROOT}/Frameworks/Active"
fi

Desta forma, não tenho uso lipopara criar uma estrutura gorda nem o Realm strip-frameworks.shpara remover as fatias desnecessárias ao enviar para a App Store.

odm
fonte
Contra qual você vincula?
Jaka Jančar
@ JakaJančar I link contra os da ${SRCROOT}/Frameworks/Activepasta. Eles são substituídos pelas estruturas pré-compiladas corretas para a arquitetura ativa em tempo de compilação.
odm
2
Adoro! Isso é muito mais simples do que a lipoabordagem de combinar e separar .
clozach
2

basicamente para isso eu encontrei uma solução muito boa. você só precisa seguir estas etapas simples.

  1. Crie uma estrutura de toque de cacau.
  2. Defina o bitcode ativado como Não.
  3. Selecione seu alvo e escolha os esquemas de edição. Selecione Executar e escolha Liberar na guia Informações.
  4. Nenhuma outra configuração necessária.
  5. Agora construa a estrutura para qualquer simulador, pois o simulador é executado na arquitetura x86.
  6. Clique no grupo Produtos no Project Navigator e encontre o arquivo .framework.
  7. Clique com o botão direito e clique em Mostrar no localizador. Copie e cole em qualquer pasta, pessoalmente prefiro o nome 'simulador'.
  8. Agora construa a estrutura para Dispositivo iOS genérico e siga as etapas 6 a 9. Basta renomear a pasta para 'dispositivo' em vez de 'simulador'.
  9. Copie o arquivo .framework do dispositivo e cole em qualquer outro diretório. Eu prefiro o superdiretório imediato de ambos. Portanto, a estrutura do diretório agora se torna:
    • Área de Trabalho
    • dispositivo
      • MyFramework.framework
    • simulador
      • MyFramework.framework
    • MyFramework.framework Agora abra o terminal e faça o cd na área de trabalho. Agora comece a digitar o seguinte comando:

lipo -create 'device / MyFramework.framework / MyFramework' 'simulator / MyFramework.framework / MyFramework' -output 'MyFramework.framework / MyFramework'

e é isso. Aqui, mesclamos o simulador e a versão do dispositivo do binário MyFramework presente em MyFramework.framework. Obtemos uma estrutura universal que se baseia em todas as arquiteturas, incluindo simulador e dispositivo.

Nrip
fonte
Eu quero criar um arquivo FAT com bitcode habilitado. Por favor me guie.
user3898700
2

Eu só quero atualizar esta ótima resposta de @odm. Desde o Xcode 10, a CURRENT_ARCHvariável não reflete mais a arquitetura de construção. Então, mudei o script para verificar a plataforma:

echo "Copying frameworks for platform: $PLATFORM_NAME"
rm -R "${SRCROOT}/Frameworks/Active"
if [ "${PLATFORM_NAME}" = "iphonesimulator" ]; then
    cp -af "${SRCROOT}/Frameworks/Simulator/." "${SRCROOT}/Frameworks/Active"
else
    cp -af "${SRCROOT}/Frameworks/Device/." "${SRCROOT}/Frameworks/Active"
fi

Também adicionei uma linha para limpar o diretório de destino antes de copiar, porque percebi que os arquivos adicionais nos subdiretórios não seriam substituídos de outra forma.

Dorian Roy
fonte
1

Minha resposta cobre os pontos abaixo:

  • Faça uma estrutura que funcione tanto para o simulador quanto para o dispositivo

  • Como exportar o Cocoa Touch Framework “fat” (para Simulador e Dispositivo)?

  • Símbolos indefinidos para arquitetura x86_64

  • ld: símbolo (s) não encontrado (s) para arquitetura x86_64

Etapas 1: primeiro construa suas estruturas com o alvo do simulador

Etapas 2: após o sucesso do processo de construção do simulador, agora crie para sua estrutura com seleção de destino de dispositivo ou seleção de dispositivo iOS genérico

Etapa 3: agora selecione o destino da sua estrutura e, para isso, em "Build Phases", selecione "Add Run Script" e copie o código de script abaixo)

Passo 4: Agora, finalmente, construa novamente e sua estrutura estará pronta para o simulador e a compatibilidade do dispositivo. Viva !!!!

[Observação: devemos ter a estrutura compatível pronta antes da etapa 4 final (simulador e arquitetura de dispositivo compatível, caso contrário, siga as etapas 1 e 2 acima corretamente)

Veja a imagem de referência:

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Coloque o código abaixo na área do shell:

#!/bin/sh


UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal


# make sure the output directory exists

mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"


# Step 1. Build Device and Simulator versions

xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos  BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build

xcodebuild -target "${PROJECT_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build


# Step 2. Copy the framework structure (from iphoneos build) to the universal folder

cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"


# Step 3. Copy Swift modules from iphonesimulator build (if it exists) to the copied framework directory

SIMULATOR_SWIFT_MODULES_DIR="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/."

if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then

cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule"

fi


# Step 4. Create universal binary file using lipo and place the combined executable in the copied framework directory

lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}"


# Step 5. Convenience step to copy the framework to the project's directory

cp -R "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework" "${PROJECT_DIR}"


# Step 6. Convenience step to open the project's directory in Finder

open "${BUILD_DIR}/${CONFIGURATION}-universal"

Sandip Patel - SM
fonte
Este script parece chamar a si mesmo, causando um loop infinito !! Tive que reiniciar meu computador depois de executá-lo! Estava continuamente gerando novos processos xcodebuild ... e abrindo novas janelas de busca - votaria abaixo
J.beenie
Verifique a resposta de @ l0gg3r neste SO Q / A para um script semelhante sem o problema de recursão.
J.beenie