引言 由于Xcode11引入了XCFramework 这个库概念,所以我们看到的二进制第三方库中都包含了两个文件,一个是.framework,而另外一个则是.xcframework,为什么多出了一个xcframework的文件,这个文件代表的意思是什么呢,以及如何制作这种新类型的二进制包?那首先我们必须先来了解几个概念。
Library 作用:
App的设计为了达到共享复制的目的,那么一般是把通用的函数或者功能模块制作成一个公共库。
定义:
目标文件的集合,即将相关的目标文件打包在一起,就成了一个 Library。
静态库(Static Library) 文件形式:
.a,.lib 的形式存在,iOS中还可以是.framework或.xcframework
使用原理:
App自身的代码被编译成目标文件后,通过静态链接器将App的目标文件与静态库合并,并生成可执行文件。这样,App自身代码生成的目标文件与静态库都被拷贝到可执行文件中,从而让静态库也成为了App可执行文件的一部分。
特点包括:
App启动时就全部加入内存,由于使用的是静态库,不需要从外部链接加载,所以加载速度很快,但是又因为增加了内容过多导致启动速度慢
静态库文件会被静态链接器链接并复制到生成的可执行文件中,导致可执行文件增大,占用内存也会增多
由于静态库是制作好的二进制库,如果发生变更,需要重新编译,给维护带来不便
动态库(Dynamic Library) 文件形式:
以.dylib,.so,.dll的形式存在,iOS中还可以是.framework或.xcframework
iOS App的动态库存放在.app bundle下的Frameworks 文件夹
使用原理:
以 OS X 为例,当 App 启动时,操作系统内核会将 App 代码和数据载入新进程(也就是操作系统为 App 创建的新进程)的地址空间。与此同时呢,操作系统内核也会把动态加载器(Dynamic Loader) 载入进程,由动态加载器来完成加载App依赖的动态库。不过在启动阶段,动态加载器只会根据静态链接器中记录的 App 已链接的依赖库的名字,然后使用依赖库的 install name来查找它们是否在文件系统中存在。如果不存在或不兼容,App 启动过程会中断。动态库被完全载入内存,是在代码里使用它的时候。所以相对静态库来说,使用动态库链接的 App 启动过程会更快。
特点包括:
Framework Framework通俗的理解为封装了共享资源的具有层次结构的文件夹。共享资源可以是 nib文件、国际化字符串文件、头文件、库文件等等。它同时也是个Bundle,里面的内容可以通过 Bundle相关 API来访问。Framework可以是static framework或dynamic framework。在iOS App打包完成后,如果Framework包含了模拟器指令集(x86_64 或 i386),那么用 Xcode 发布 App 的时候,会报unsupported architectures的错误,所以需要我们手动或脚本去移除。
XCFramework XCFramework是由Xcode创建的一个可分发的二进制包,包含了framework或library 的一个或多个变体,因此可以在多个平台(iOS、macOS、tvOS、watchOS)上使用,包括模拟器。XCFramework可以是静态的也可以是动态的。XCFramework的好处就是用 Xcode 发布的时候,Xcode会自动选用正确的指令集Frameworks,省去了手动移除动态库中的模拟器指令集的工作。
制作 Frameworks 假设您的framework代码都已经写好了,打包的Aggregation Target也创建好了。接下来,我将直接讲用脚本来制作frameworks。至于是制作static framework还是dynamic framework可以在framework target的 Build Settings中的Mach-O Type选择framework的类型,一般选用Dynamic Library或者Static Library就行。
开发环境 Xcode11.0+
macOS Catalina 10.15.0+
.xcarchive 目录结构 制作universal framework与xcframework,我们都会用到.xcarchive包,所以我们先来看下它的目录结构
制作Universal Framework脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 #!/bin/sh -e REVEAL_FRAMEWORK_IN_FINDER=true BUILD_DIR=\"\${PROJECT_DIR}/Build\" FREAMEWORK_NAME=\"\${PROJECT_NAME}\" FREAMEWORK_OUTPUT_DIR=\"\${PROJECT_DIR}/Distribution\" ARCHIVE_PATH_IOS_DEVICE=\"\${BUILD_DIR}/ios_device.xcarchive \" ARCHIVE_PATH_IOS_SIMULATOR=\"\${BUILD_DIR}/ios_simulator.xcarchive \" \# ARCHIVE_PATH_MACOS=\"./build/macos.xcarchive \" function archiveOnePlatform { echo \"▸ Starts archiving the scheme: \${1 } for destination: \${2 };\\n▸ Archive path: \${3 }\" xcodebuild archive \\ -scheme \"\${1 }\" \\ -destination \"\${2 }\" \\ -archivePath \"\${3 }\" \\ VALID_ARCHS=\"\${4 }\" \\ SKIP_INSTALL=NO \\ BUILD_LIBRARY_FOR_DISTRIBUTION=YES \| xcpretty \# sudo gem install -n /usr/local/bin xcpretty \# xcpretty makes xcode compile information much more readable. } function archiveAllPlatforms { \# https://www.mokacoding.com/blog/xcodebuild-destination-options/ \# Platform Destination \# iOS generic/platform=iOS \# iOS Simulator generic/platform=iOS Simulator \# iPadOS generic/platform=iPadOS \# iPadOS Simulator generic/platform=iPadOS Simulator \# macOS generic/platform=macOS \# tvOS generic/platform=tvOS \# watchOS generic/platform=watchOS \# watchOS Simulator generic/platform=watchOS Simulator \# carPlayOS generic/platform=carPlayOS \# carPlayOS Simulator generic/platform=carPlayOS Simulator SCHEME=\${1 } archiveOnePlatform \$SCHEME \"generic/platform=iOS Simulator\" \${ARCHIVE_PATH_IOS_SIMULATOR} \"x86_64\" archiveOnePlatform \$SCHEME \"generic/platform=iOS\" \${ARCHIVE_PATH_IOS_DEVICE} \"armv7 arm64\" \# archiveOnePlatform \$SCHEME \"generic/platform=macOS\" \${ARCHIVE_PATH_MACOS} } function makeUniversalFramework { FRAMEWORK_RELATIVE_PATH=\"Products/Library/Frameworks\" SIMULATOR_FRAMEWORK=\"\${ARCHIVE_PATH_IOS_SIMULATOR}/\${FRAMEWORK_RELATIVE_PATH}/\${FREAMEWORK_NAME}.framework \" DEVICE_FRAMEWORK=\"\${ARCHIVE_PATH_IOS_DEVICE}/\${FRAMEWORK_RELATIVE_PATH}/\${FREAMEWORK_NAME}.framework \" OUTPUT_FRAMEWORK=\"\${FREAMEWORK_OUTPUT_DIR}/\${FREAMEWORK_NAME}.framework \" mkdir -p \"\${OUTPUT_FRAMEWORK}\" \# Copy all the contents of iphoneos framework to output framework dir. cp -rf \"\${DEVICE_FRAMEWORK}/.\" \"\${OUTPUT_FRAMEWORK}\" lipo \"\${SIMULATOR_FRAMEWORK}/\${FREAMEWORK_NAME}\" \"\${DEVICE_FRAMEWORK}/\${FREAMEWORK_NAME}\" \\ -create -output \"\${OUTPUT_FRAMEWORK}/\${FREAMEWORK_NAME}\" \# For Swift framework, Swiftmodule needs to be copied in the universal framework if \[ -d\"\${SIMULATOR_FRAMEWORK}/Modules/\${FRAMEWORK_NAME}.swiftmodule/\" \]; then cp -f \"\${SIMULATOR_FRAMEWORK}/Modules/\${FRAMEWORK_NAME}.swiftmodule/\*\" \"\${OUTPUT_FRAMEWORK}/Modules/\${FRAMEWORK_NAME}.swiftmodule/\" \| echo fi if \[ -d \"\${DEVICE_FRAMEWORK}/Modules/\${FRAMEWORK_NAME}.swiftmodule/\" \]; then cp -f \"\${DEVICE_FRAMEWORK}/Modules/\${FRAMEWORK_NAME}.swiftmodule/\*\" \"\${OUTPUT_FRAMEWORK}/Modules/\${FRAMEWORK_NAME}.swiftmodule/\" \| echo fi } echo \"#####################\" echo \"▸ Cleaning Framework output dir: \${FREAMEWORK_OUTPUT_DIR}\" rm -rf \"\$FREAMEWORK_OUTPUT_DIR\" \#### Make Dynamic Framework echo \"▸ Archive framework: \${FREAMEWORK_NAME}\" archiveAllPlatforms \"\$FREAMEWORK_NAME\" echo \"▸ Make universal framework: \${FREAMEWORK_NAME}.framework\" makeUniversalFramework \# Clean Build rm -rf \"{\$BUILD_DIR}\" if \[ \${REVEAL_FRAMEWORK_IN_FINDER} = true \]; then open \"\${FREAMEWORK_OUTPUT_DIR}/\" fi
编译单个平台的函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 \# 制作完 framework 后,是否在 Finder 中打开 REVEAL_FRAMEWORK_IN_FINDER=true \# Framework 的名字 FREAMEWORK_NAME=\"\${PROJECT_NAME}\" \# 制作好的 framework 会输出到这个文件夹下面 FREAMEWORK_OUTPUT_DIR=\"\${PROJECT_DIR}/Distribution\" \# Device Archive 生成的 .xcarchive 存放路径。在工程的根目录下生成 Build 文件夹。 ARCHIVE_PATH_IOS_DEVICE=\"./Build/ios_device.xcarchive \" \# Simulator Archive 生成的 .xcarchive 存放路径。 ARCHIVE_PATH_IOS_SIMULATOR=\"./Build/ios_simulator.xcarchive \" \# 我们可以编译更多平台的 xcarchive \# ARCHIVE_PATH_MACOS=\"./build/macos.xcarchive \" \# 生成单个平台的 .xcarchive . 接收4 个参数, scheme, destination, archivePath,指令集. \# xcpretty 可以删除,这里用来使 Xcode 输出的日志更加人性化。 function archiveOnePlatform { echo \"▸ Starts archiving the scheme: \${1 } for destination: \${2 };\\n▸ Archive path: \${3 }\" xcodebuild archive \\ -scheme \"\${1 }\" \\ -destination \"\${2 }\" \\ -archivePath \"\${3 }\" \\ VALID_ARCHS=\"\${4 }\" \\ SKIP_INSTALL=NO \\ BUILD_LIBRARY_FOR_DISTRIBUTION=YES \| xcpretty \# BUILD_LIBRARY_FOR_DISTRIBUTION=YES \# sudo gem install -n /usr/local/bin xcpretty \# xcpretty makes xcode compile information much more readable. }
编译所有平台的函数 以下方法可以编译并生成iOS device, simulator两个平台的.xcarchive,此方法接收一个参数:scheme, 即对应app target的scheme。通常情况下,scheme和framework name是相同的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 function archiveAllPlatforms { \# https://www.mokacoding.com/blog/xcodebuild-destination-options/ \# Platform Destination \# iOS generic/platform=iOS \# iOS Simulator generic/platform=iOS Simulator \# iPadOS generic/platform=iPadOS \# iPadOS Simulator generic/platform=iPadOS Simulator \# macOS generic/platform=macOS \# tvOS generic/platform=tvOS \# watchOS generic/platform=watchOS \# watchOS Simulator generic/platform=watchOS Simulator \# carPlayOS generic/platform=carPlayOS \# carPlayOS Simulator generic/platform=carPlayOS Simulator SCHEME=\${1 } archiveOnePlatform \$SCHEME \"generic/platform=iOS Simulator\" \${ARCHIVE_PATH_IOS_SIMULATOR} \"x86_64\" archiveOnePlatform \$SCHEME \"generic/platform=iOS\" \${ARCHIVE_PATH_IOS_DEVICE} \"armv7 arm64\" \# archiveOnePlatform \$SCHEME \"generic/platform=macOS\" \${ARCHIVE_PATH_MACOS} }
这个方法执行完后,在本例中会得到以下Build文件夹内的内容:
生成Universal Framework的函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 function makeUniversalFramework { \# xcarchive 包中的 Frameworks 目录相对路径 FRAMEWORK_RELATIVE_PATH=\"Products/Library/Frameworks\" \# 接下来的三个路径分别是模拟器平台的framework路径,真机平台的framework路径,以及输出的universal framework路径 SIMULATOR_FRAMEWORK=\"\${ARCHIVE_PATH_IOS_SIMULATOR}/\${FRAMEWORK_RELATIVE_PATH}/\${FREAMEWORK_NAME}.framework \" DEVICE_FRAMEWORK=\"\${ARCHIVE_PATH_IOS_DEVICE}/\${FRAMEWORK_RELATIVE_PATH}/\${FREAMEWORK_NAME}.framework \" OUTPUT_FRAMEWORK=\"\${FREAMEWORK_OUTPUT_DIR}/\${FREAMEWORK_NAME}.framework \" mkdir -p \"\${OUTPUT_FRAMEWORK}\" \# Copy all the contents of iphoneos framework to output framework dir. cp -rf \"\${DEVICE_FRAMEWORK}/.\" \"\${OUTPUT_FRAMEWORK}\" lipo \"\${SIMULATOR_FRAMEWORK}/\${FREAMEWORK_NAME}\" \"\${DEVICE_FRAMEWORK}/\${FREAMEWORK_NAME}\" \\ -create -output \"\${OUTPUT_FRAMEWORK}/\${FREAMEWORK_NAME}\" \# For Swift framework, Swiftmodule needs to be copied in the universal framework if \[ -d\"\${SIMULATOR_FRAMEWORK}/Modules/\${FRAMEWORK_NAME}.swiftmodule/\" \]; then cp -f \"\${SIMULATOR_FRAMEWORK}/Modules/\${FRAMEWORK_NAME}.swiftmodule/\*\" \"\${OUTPUT_FRAMEWORK}/Modules/\${FRAMEWORK_NAME}.swiftmodule/\" \| echo fi if \[ -d \"\${DEVICE_FRAMEWORK}/Modules/\${FRAMEWORK_NAME}.swiftmodule/\" \]; then cp -f \"\${DEVICE_FRAMEWORK}/Modules/\${FRAMEWORK_NAME}.swiftmodule/\*\" \"\${OUTPUT_FRAMEWORK}/Modules/\${FRAMEWORK_NAME}.swiftmodule/\" \| echo fi }
开始制作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 echo \"#####################\" echo \"▸ Cleaning Framework output dir: \${FREAMEWORK_OUTPUT_DIR}\" rm -rf \"\$FREAMEWORK_OUTPUT_DIR\" echo \"▸ Archive framework: \${FREAMEWORK_NAME}\" archiveAllPlatforms \"\$FREAMEWORK_NAME\" echo \"▸ Make universal framework: \${FREAMEWORK_NAME}.framework \" makeUniversalFramework \# Clean Build rm -rf \"./Build\" if \[ \${REVEAL_FRAMEWORK_IN_FINDER} = true \] ; then open \"\${FREAMEWORK_OUTPUT_DIR}/\" fi
去除动态库中的模拟器指令集 App打包过程中,需要将App依赖的动态库中的模拟器指令集去除,这里是完整的脚本, 如何使用,请点Stripping unwanted architectures from dynamic libraries。在app target 的 Build Phase下新建Run Script,并放到Embed Frameworks下面,然后将脚本复制进去就行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #!/bin/sh APP_PATH=\"\${TARGET_BUILD_DIR}/\${WRAPPER_NAME}\" echo \$APP_PATH \# This script loops through the frameworks embedded in the application and \# removes unused architectures. find \"\$APP_PATH\" -name \'\*.framework \' -type d \| while read -r FRAMEWORK do FRAMEWORK_EXECUTABLE_NAME=\$(defaults read \"\$FRAMEWORK/Info.plist \" CFBundleExecutable) FRAMEWORK_EXECUTABLE_PATH=\"\$FRAMEWORK/\$FRAMEWORK_EXECUTABLE_NAME\" echo \"Executable is \$FRAMEWORK_EXECUTABLE_PATH\" EXTRACTED_ARCHS=() for ARCH in \$ARCHS do echo \"Extracting \$ARCH from \$FRAMEWORK_EXECUTABLE_NAME\" lipo -extract \"\$ARCH\" \"\$FRAMEWORK_EXECUTABLE_PATH\" -o \"\$FRAMEWORK_EXECUTABLE_PATH-\$ARCH\" EXTRACTED_ARCHS+=(\"\$FRAMEWORK_EXECUTABLE_PATH-\$ARCH\") done echo \"Merging extracted architectures: \${ARCHS}\" lipo -o \"\$FRAMEWORK_EXECUTABLE_PATH-merged\" -create \"\${EXTRACTED_ARCHS\[@\] }\" rm \"\${EXTRACTED_ARCHS\[@\] }\" echo \"Replacing original executable with thinned version\" rm \"\$FRAMEWORK_EXECUTABLE_PATH\" mv \"\$FRAMEWORK_EXECUTABLE_PATH-merged\" \"\$FRAMEWORK_EXECUTABLE_PATH\" done
制作XCFramework脚本 XCFramework脚本制作与Universal Framework流程方式一致,存在生成规则的不同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 #!/bin/sh -e REVEAL_XCFRAMEWORK_IN_FINDER=true FREAMEWORK_NAME=\"\${PROJECT_NAME}\" FREAMEWORK_OUTPUT_DIR=\"\${PROJECT_DIR}/Distribution\" ARCHIVE_PATH_IOS_DEVICE=\"./Build/ios_device.xcarchive \" ARCHIVE_PATH_IOS_SIMULATOR=\"./Build/ios_simulator.xcarchive \" \# ARCHIVE_PATH_MACOS=\"./build/macos.xcarchive \" function archiveOnePlatform { echo \"▸ Starts archiving the scheme: \${1 } for destination: \${2 };\\n▸ Archive path: \${3 }\" xcodebuild archive \\ -scheme \"\${1 }\" \\ -destination \"\${2 }\" \\ -archivePath \"\${3 }\" \\ VALID_ARCHS=\"\${4 }\" \\ SKIP_INSTALL=NO \\ BUILD_LIBRARY_FOR_DISTRIBUTION=YES \| xcpretty \# sudo gem install -n /usr/local/bin xcpretty \# xcpretty makes xcode compile information much more readable. } function archiveAllPlatforms { \# https://www.mokacoding.com/blog/xcodebuild-destination-options/ \# Platform Destination \# iOS generic/platform=iOS \# iOS Simulator generic/platform=iOS Simulator \# iPadOS generic/platform=iPadOS \# iPadOS Simulator generic/platform=iPadOS Simulator \# macOS generic/platform=macOS \# tvOS generic/platform=tvOS \# watchOS generic/platform=watchOS \# watchOS Simulator generic/platform=watchOS Simulator \# carPlayOS generic/platform=carPlayOS \# carPlayOS Simulator generic/platform=carPlayOS Simulator SCHEME=\${1 } archiveOnePlatform \$SCHEME \"generic/platform=iOS Simulator\" \${ARCHIVE_PATH_IOS_SIMULATOR} \"x86_64\" archiveOnePlatform \$SCHEME \"generic/platform=iOS\" \${ARCHIVE_PATH_IOS_DEVICE} \"armv7 arm64\" \# archiveOnePlatform \$SCHEME \"generic/platform=macOS\" \${ARCHIVE_PATH_MACOS} } function makeXCFramework { FRAMEWORK_RELATIVE_PATH=\"Products/Library/Frameworks\" OUTPUT_DIR=\"\${FREAMEWORK_OUTPUT_DIR}/DynamicFramework\" mkdir -p \"\${OUTPUT_DIR}\" xcodebuild -create-xcframework \\ -framework \"\${ARCHIVE_PATH_IOS_DEVICE}/\${FRAMEWORK_RELATIVE_PATH}/\${FREAMEWORK_NAME}.framework \" \\ -framework \"\${ARCHIVE_PATH_IOS_SIMULATOR}/\${FRAMEWORK_RELATIVE_PATH}/\${FREAMEWORK_NAME}.framework \" \\ -output \"\${OUTPUT_DIR}/\${FREAMEWORK_NAME}.xcframework \" } echo \"#####################\" echo \"▸ Cleaning XCFramework output dir: \${FREAMEWORK_OUTPUT_DIR}\" rm -rf \$FREAMEWORK_OUTPUT_DIR \#### Make XCFramework echo \"▸ Archive framework: \${FREAMEWORK_NAME}\" archiveAllPlatforms \$FREAMEWORK_NAME echo \"▸ Make framework: \${FREAMEWORK_NAME}.xcframework \" makeXCFramework \# Clean Build rm -rf \"./Build\" if \[ \${REVEAL_XCFRAMEWORK_IN_FINDER} = true \] ; then open \"\${FREAMEWORK_OUTPUT_DIR}/\" fi
生成Universal XCFramework的函数 制作XCFramewrok的时候,需要将Build Settings中的Build Libraries for Distribution设置为YES。不过即使不设置,脚本也会将其设置为YES。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function makeXCFramework { FRAMEWORK_RELATIVE_PATH=\"Products/Library/Frameworks\" OUTPUT_DIR=\"\${FREAMEWORK_OUTPUT_DIR}/DynamicFramework\" mkdir -p \"\${OUTPUT_DIR}\" xcodebuild -create-xcframework \\ -framework \"\${ARCHIVE_PATH_IOS_DEVICE}/\${FRAMEWORK_RELATIVE_PATH}/\${FREAMEWORK_NAME}.framework \" \\ -framework \"\${ARCHIVE_PATH_IOS_SIMULATOR}/\${FRAMEWORK_RELATIVE_PATH}/\${FREAMEWORK_NAME}.framework \" \\ -output \"\${OUTPUT_DIR}/\${FREAMEWORK_NAME}.xcframework \" }
开始制作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 echo \"#####################\" echo \"▸ Cleaning XCFramework output dir: \${FREAMEWORK_OUTPUT_DIR}\" rm -rf \$FREAMEWORK_OUTPUT_DIR \#### Make XCFramework echo \"▸ Archive framework: \${FREAMEWORK_NAME}\" archiveAllPlatforms \$FREAMEWORK_NAME echo \"▸ Make framework: \${FREAMEWORK_NAME}.xcframework \" makeXCFramework \# Clean Build rm -rf \"./Build\" if \[ \${REVEAL_XCFRAMEWORK_IN_FINDER} = true \] ; then open \"\${FREAMEWORK_OUTPUT_DIR}/\" fi
最后生成的xcframework:
对framework的命令处理
1 lipo -info /path/to /xxx.framework /xxx
查看dynamic framework是否支持bitcode
1 otool -arch armv7 -l /path/to /xxx.framework /xxx \| grep \_\_bundle
如果包含 bitcode, 你会看到sectname __bundle信息。如果不包含,Terminal 输出为空
查看static framework是否支持 bitcode
1 otool -arch armv7 -l /path/to /xxx.framework /xxx \| grep \_\_bitcode
同上输出信息
参考资料 [使用 Xcode 制作 Framework 与 XCFramework - 简书]