如何导出“胖”Cocoa Touch Framework(用于模拟器和设备)?

时间:2015-04-14 18:08:44

标签: ios xcode cocoa-touch frameworks ios-universal-framework

使用 Xcode 6 ,我们可以创建自己的动态Cocoa Frameworks

enter image description here

因为:

  • 模拟器仍然使用32-bit

  •   从2015年6月1日开始,提交到App Store的应用更新必须包含64位支持,并使用iOS 8 SDK(developer.apple.com)构建

我们必须使胖库在设备和模拟器上运行项目。即支持框架中的32位和64位。

但我没有找到任何手册,如何导出通用胖框架以便将来与其他项目集成(并与某人共享此库)。

以下是我重现的步骤:

  1. ONLY_ACTIVE_ARCH=NO

    中设置Build Settings

    enter image description here

  2. 将支持armv7 armv7s arm64 i386 x86_64添加到Architectures(肯定)

  3. enter image description here

    1. 构建框架并在Finder中打开它:
    2. enter image description here enter image description here

      1. 将此框架添加到另一个项目
      2. 实际结果:

        但最终我仍然遇到在设备和模拟器上同时使用此框架运行项目的问题。

        • 如果我从Debug-iphoneos文件夹中获取框架 - 它可以在设备上运行 并在模拟器上获取错误:ld: symbol(s) not found for architecture i386

          xcrun lipo -info CoreActionSheetPicker
          
            

          胖文件中的架构:CoreActionSheetPicker是:armv7 armv7s arm64

        • 如果我从Debug-iphonesimulator文件夹中获取框架 - 它适用于模拟器。我在设备上遇到错误:ld: symbol(s) not found for architecture arm64

          xcrun lipo -info CoreActionSheetPicker
          
            

          胖文件中的架构:CoreActionSheetPicker是:i386 x86_64

        那么,如何创建一个适用于设备和模拟器的动态框架?

        此答案与Xcode 6 iOS Creating a Cocoa Touch Framework - Architectures issues有关,但并非重复。


        更新

        我发现了这种情况的“肮脏黑客”。查看我的answer below。 如果有人知道更方便的方式 - 请告诉我!

6 个答案:

答案 0 :(得分:78)

这个答案的实际情况是:2015年7月。事情很可能会发生变化。

TLDR;

目前Xcode没有自动导出通用胖框架的工具,因此开发人员必须使用lipo工具的手动使用。同样根据this radar提交给AppStore开发人员之前,框架的消费者也必须使用lipo从框架中剥离模拟器切片。

更长的答案如下

我在这个主题上做了类似的研究(答案底部的链接)。

我没有找到任何关于分发的官方文档,因此我的研究基于对Apple Developer Forums,Carthage和Realm项目的探索以及我自己的xcodebuildlipo,{{1}的实验工具。

这是来自Apple Developer论坛帖子Exporting app with embedded framework的长引用(带有一点标记):

  

从框架项目中导出框架的正确方法是什么?

     

目前唯一的方法就是你所做的:

     
      
  • 为模拟器和iOS设备构建目标。
  •   
  • 导航到该项目的Xcode的DerivedData文件夹,并将两个二进制文件组合成一个单独的框架。但是,在Xcode中构建框架目标时,请确保将目标设置“仅构建活动体系结构”调整为“否”。这将允许Xcode为多种binarty类型(arm64,armv7等)构建目标。这就是为什么它可以从Xcode运行而不是作为一个独立的二进制文件。

  •   
  • 此外,您还需要确保将方案设置为发布版本并构建针对发布的框架目标。   如果仍然遇到库未加载错误,请检查框架中的代码片。

  •   
  • 使用codesign并检查结果。
  •   
     

lipo -info MyFramworkBinary

     

结果为lipo -info MyFrameworkBinary

     
      
  • 现代通用框架将包含4个切片,但可能包含更多:i386 x86_64 armv7 arm64   如果您没有看到至少这4,那可能是因为Build Active Architecture设置。
  •   

这描述了与@skywinder在答案中所做的完全相同的过程。

这是Carthage uses lipoRealm uses lipo

的方式

重要细节

雷达:Xcode 6.1.1 & 6.2: iOS frameworks containing simulator slices can't be submitted to the App Store以及围绕Realm#1163Carthage#188进行的长时间讨论,结束于特殊的解决方法:

在提交到AppStore之前

必须从模拟器切片中删除iOS框架二进制文件

Carthage有特殊代码:CopyFrameworks和相应的文档:

  

此脚本适用于由通用二进制文件触发的App Store submission bug

Realm有特殊脚本:strip-frameworks.sh和相应的文档:

  

归档通用二进制文件时,需要执行此步骤App Store submission bug

还有好文章:Stripping Unwanted Architectures From Dynamic Libraries In Xcode

我自己使用了Realm的i386 x86_64 armv7 arm64,它完美地为我工作而没有任何修改,当然任何人都可以从头开始写一个。

我建议阅读的主题链接,因为它包含此问题的另一个方面:代码签名 - Creating iOS/OSX Frameworks: is it necessary to codesign them before distributing to other developers?

答案 1 :(得分:56)

这不是那么明确的解决方案,但我找到了唯一的方法:

  1. ONLY_ACTIVE_ARCH=NO

  2. 中设置Build Settings
    • 为模拟器构建库
    • 为设备构建库
  3. 在您的框架的控制台Products文件夹中打开(您可以通过开放框架文件夹和cd ..从那里打开它)

  4. enter image description here enter image description here

    1. Products文件夹中运行 this 脚本。它在此文件夹中创建fat Framework。 (或按照下面 3.4。中所述的那样手动完成)

    2. 或者:

      1. 通过此脚本使用 lipo 组合这两个框架(将YourFrameworkName替换为您的框架名称)

        lipo -create -output "YourFrameworkName" "Debug-iphonesimulator/YourFrameworkName.framework/YourFrameworkName" "Debug-iphoneos/YourFrameworkName.framework/YourFrameworkName"
        
      2. 替换现有框架中的新二进制文件:

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

        1. 利润:./YourFrameworkName.framework - 是现成的胖二进制文件!您可以将其导入项目!

        2. 对于项目,不在工作区中:

          您还可以尝试使用this gist所述的here。但似乎它不适用于工作空间中的项目。

答案 2 :(得分:10)

@Stainlav的回答非常有用,但我做的是编译框架的两个版本(一个用于设备,一个用于模拟器),然后添加以下Run Script Phase以自动复制所需的预编译框架正在运行的架构

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

这样我就没有使用lipo来创建一个胖框架,而不是Realm的strip-frameworks.sh来提交到App Store时删除不必要的切片。

答案 3 :(得分:2)

基本上为此我找到了非常好的解决方案。你只需要遵循这些简单的步骤。

  1. 创建一个可可触摸框架。
  2. 将bitcode设置为No。
  3. 选择目标并选择编辑方案。选择Run并从Info选项中选择Release。
  4. 无需其他设置。
  5. 现在为任何模拟器构建框架,因为模拟器在x86架构上运行。
  6. 单击Project Navigator中的Products组,找到.framework文件。
  7. 右键单击它并单击“在查找器中显示”。将其复制并粘贴到任何文件夹中,我个人更喜欢名称“模拟器”。
  8. 现在为Generic iOS Device构建框架并按照步骤6到9进行操作。只需将文件夹重命名为“device”而不是“simulator”。
  9. 复制设备.framework文件并粘贴到任何其他目录中。我更喜欢两者的直接超级目录。 所以目录结构现在变成:
    • 桌面
    • 设备
      • MyFramework.framework
    • 模拟器
      • MyFramework.framework
    • MyFramework.framework 现在打开终端并cd到桌面。现在开始输入以下命令:
  10.   

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

    就是这样。在这里,我们合并MyFramework.framework中存在的MyFramework二进制文件的模拟器和设备版本。我们获得了一个通用框架,可以为所有架构构建,包括模拟器和设备。

答案 4 :(得分:2)

我只想通过@odm更新this great answer。从Xcode 10开始,CURRENT_ARCH变量不再反映构建体系结构。所以我改为更改脚本来检查平台:

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

我还添加了一行以在复制之前清除目标目录,因为我注意到否则子目录中的其他文件不会被覆盖。

答案 5 :(得分:1)

我的答案涵盖以下几点:

  • 制作适用于模拟器和设备的框架

  • 如何导出“胖”Cocoa Touch Framework(适用于模拟器和设备)?

  • 架构的未定义符号x86_64

  • ld:找不到架构x86_64

  • 的符号

步骤1:首先使用Simulator目标构建框架

步骤2:模拟器构建过程成功后,现在为您的框架构建设备目标选择或通用iOS设备选择

步骤3:现在选择您的框架目标,并在“Build Phases”下选择“Add Run Script”并复制以下脚本代码)

Step4:最后,再次构建,您的框架已准备好兼容模拟器和设备。乌拉!!!!

[注意:我们必须在最终步骤4之前准备好两个兼容框架(模拟器和设备架构兼容,如果没有,请正确执行上述步骤1和2)

参见参考图片:

enter image description here

enter image description here

将以下代码放入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"