如何导出 "fat" Cocoa Touch Framework(用于模拟器和设备)?

How to export "fat" Cocoa Touch Framework (for Simulator and Device)?

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

因为:

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

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

这是我的重现步骤:

  1. Build Settings

    中设置ONLY_ACTIVE_ARCH=NO

  2. 添加支持 armv7 armv7s arm64 i386 x86_64Architectures(当然)

  1. 构建框架并在 Finder 中打开它:

  1. 将此框架添加到另一个项目

实际结果:

但最后我还是遇到了运行同时在设备和模拟器上使用这个框架的项目问题。

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

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


更新:

我发现了这个案例的“肮脏黑客”。看我的。 如果有人知道更方便的方法 - 请告诉我!

这不是很明确的解决方案,但我发现唯一的办法是:

  1. 设置ONLY_ACTIVE_ARCH=NOBuild Settings

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

  1. 运行 this 来自 Products 文件夹的脚本。它会在此文件夹中创建 fat Framework。 (或按照下面 3.4. 中的说明手动执行)

或:

  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 - 是即用型 fat 二进制文件!您可以将其导入到您的项目中!

对于不在工作区中的项目:

您也可以尝试使用this gist as described here。但它似乎不适用于工作区中的项目。

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

TLDR;

目前 Xcode 没有用于自动导出通用 fat 框架的工具,因此开发人员必须求助于手动使用 lipo 工具。此外,根据 this radar 在提交给作为框架消费者的 AppStore 开发人员之前,还必须使用 lipo 从框架中剥离模拟器切片。

更长的答案如下


我在主题中做了类似的研究(答案底部的link)。

我没有找到任何关于分发的官方文档,所以我的研究基于对 Apple Developer Forums、Carthage 和 Realm 项目的探索以及我自己对 xcodebuildlipocodesign 工具。

这是来自 Apple 开发者论坛帖子 Exporting app with embedded framework:

的长引述(带有我的一些标记)

What is the proper way to export a framework from a framework project?

Currently the only way is exactly what you have done:

  • Build the target for both simulator and iOS device.
  • Navigate to Xcode's DerivedData folder for that project and lipo the two binaries together into one single framework. However, when you build the framework target in Xcode, make sure to adjust the target setting 'Build Active Architecture Only' to 'NO'. This will allow Xcode to build the target for multiple binarty types (arm64, armv7, etc). This would be why it works from Xcode but not as a standalone binary.

  • Also you'll want to make sure the scheme is set to a Release build and build the framework target against release. If you are still getting a library not loaded error, check the code slices in the framework.

  • Use lipo -info MyFramworkBinary and examine the result.

lipo -info MyFrameworkBinary

Result is i386 x86_64 armv7 arm64

  • Modern universal frameworks will include 4 slices, but could include more: i386 x86_64 armv7 arm64 If you don't see at least this 4 it coud be because of the Build Active Architecture setting.

这描述的过程与@skywinder 在他的回答中所做的几乎相同。

就是这样Carthage uses lipo and Realm uses lipo


重要细节

有雷达:Xcode 6.1.1 & 6.2: iOS frameworks containing simulator slices can't be submitted to the App Store and a long discussion around it on Realm#1163 and Carthage#188 以特殊解决方法结束:

在提交到 AppStore 之前 iOS 必须从模拟器切片中剥离框架二进制文件

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

This script works around an App Store submission bug triggered by universal binaries.

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

This step is required to work around an App Store submission bug when archiving universal binaries.

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

我自己使用了 Realm 的 strip-frameworks.sh,它对我来说很完美,没有任何修改,当然任何人都可以从头开始写一个。


我推荐阅读我主题的 link,因为它包含此问题的另一个方面:代码签名 -

@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 时删除不必要的切片。

基本上为此我找到了很好的解决方案。您只需按照这些简单的步骤操作即可。

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

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

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

我的回答包括以下几点:

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

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

  • 体系结构的未定义符号x86_64

  • ld: 找不到体系结构的符号 x86_64

第 1 步:首先使用模拟器目标构建框架

第 2 步:模拟器构建过程成功后,现在使用设备目标 selection 或通用 iOS 设备 selection

为您的框架构建

第 3 步:现在 select 您的框架目标以及在 "Build Phases" select "Add Run Script" 下并复制以下脚本代码)

第 4 步:最后,再次构建,您的框架已准备好兼容模拟器和设备。欢呼!!!!

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

查看参考图片:

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

我只想通过@odm 更新。自 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

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