我的博客原文地址
Framework 是资源的集合,将静态库和其头文件包含到一个结构中,让 Xcode 可以方便地把它纳入到你的项目中。
在运行时,库中按你的想法暴露需要的头文件,整个工程都可以调用暴露出来的接口和参数,这样减少了内存消耗,提高了系统的性能。
与别人分享自己开发的组件,有两种方式。
第一种方式容易被人看到具体实现的细节,这些可能是你不想暴露出来的。此外,开发者也可能并不想看到你的所有代码,而仅仅是希望将功能的一部分植入到自己的应用中。
因此很多组件的封装采用第二种方式,这也是下文介绍的主要内容。
步骤1:打开 Xcode ,依次点击 Create a new Xcode project → iOS → Cocoa Touch Framework
,在 Product Name
中填写名称。(注:这就是最后 framework 的名称。)
步骤2:假如你的项目依赖某些系统库,那么需要通过点击 Targets → Build Phases → Link Binary with Libraries
,点击 +
符号将它们添加到工程中。
步骤3:修改 Project 中的 iOS Deployment Target 版本号,选择你的框架最低支持的 iOS 版本。
步骤4:如果组件中存在 .xib
文件,请确保 TARGETS → Build Phases → Copy Bundle Resources
下存在该 xib 文件。
步骤5:将封装好的组件文件夹拖入到项目目录下,选择你要公开的头文件。
步骤6:依次点击 TARGETS → Build Phases → Headers
,目录下有:
选中 Scheme 选择当前项目,然后右边设备依次选中 Generic iOS Device(通用真机版本)和任一模拟器,分别编译(command + B),成功将会自动跳转到打出的 .framework
文件相应的目录下。
为了让用户能统一调用一个 framework ,还需要将二者合成为一个 framework 。这里介绍一种简单的方法:
1、新建一个 target,依次点击 TARGETS 左下角的加号按钮 → Cross-platform → Other 下的 Aggregate
。
2、点击工程文件,选 TARGETS → 刚才创建的 Aggregate → Build Phases → + → New Run Script Phases
。在当前栏目里会多出一个 Run Script
,在里面输入以下脚本:
#!/bin/sh
#要build的target名
TARGET_NAME=${PROJECT_NAME}
if [[ $1 ]]
then
TARGET_NAME=$1
fi
UNIVERSAL_OUTPUT_FOLDER="${SRCROOT}/${PROJECT_NAME}/"
#创建输出目录,并删除之前的 framework 文件
mkdir -p "${UNIVERSAL_OUTPUT_FOLDER}"
rm -rf "${UNIVERSAL_OUTPUT_FOLDER}/${TARGET_NAME}.framework"
#分别编译模拟器和真机的 framework
xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphonesimulator BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
#拷贝 framework 到 univer 目录
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework" "${UNIVERSAL_OUTPUT_FOLDER}"
#合并 framework,输出最终的 framework 到 build 目录
lipo -create -output "${UNIVERSAL_OUTPUT_FOLDER}/${TARGET_NAME}.framework/${TARGET_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGET_NAME}.framework/${TARGET_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${TARGET_NAME}.framework/${TARGET_NAME}"
#删除编译之后生成的无关的配置文件
dir_path="${UNIVERSAL_OUTPUT_FOLDER}/${TARGET_NAME}.framework/"
for file in ls $dir_path
do
if [[ ${file} =~ ".xcconfig" ]]
then
rm -f "${dir_path}/${file}"
fi
done
#判断 build 文件夹是否存在,存在则删除
if [ -d "${SRCROOT}/build" ]
then
rm -rf "${SRCROOT}/build"
fi
rm -rf "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator" "${BUILD_DIR}/${CONFIGURATION}-iphoneos"
#打开合并后的文件夹
open "${UNIVERSAL_OUTPUT_FOLDER}"
3、使用脚本进行编译 (command + B),成功后将会自动跳转到打出的 .framework
文件相应的目录下。
步骤1:打开终端,进入到你的 framework 文件所在的目录
cd ${yourFrameworkName}.framework
步骤2:查看架构支持
lipo -info ${yourFrameworkName}.framework/${yourFrameworkName}
可以看到输出:
Architectures in the fat file: ${yourFrameworkName} are: i386 x86_64 armv7 arm64 (支持的架构显示在这)
设备的CPU架构(指令集)
引入的 framework 里存在分类的话,编译运行项目会报形如 xxx unrecognized selector sent to class xxx
的错误。
解决办法:
选中左边栏的项目文件,然后依次点击 Targets → Build Settings → Linking → Other Linker Flags
,在里面添加 -ObjC
再次编译就能正常运行。
补充知识:
从 C 代码到可执行文件经历编译步骤是:
源代码 > 预处理器 > 编译器 > 汇编器 > 机器码 > 链接器 > 可执行文件。
在最后一步需要把 .o
文件和 C 语言运行库链接起来,这时需要用到 ld
命令。源文件经过一系列处理后,会生成对应的 .obj
文件,一个项目必然会有多个 .obj
文件,并且这些文件之间存在各种联系,如函数调用等。链接器做的事就是把目标文件和所用的一些库链接在一起形成一个完整的可执行文件。Other Linker Flags
设置的值实际上就是 ld
命令执行时后面所加的参数。下面介绍3个常用参数:
参数 | 描述 |
---|---|
-ObjC | 链接器会把静态库中所有的 Objective-C 类和分类都加载到最后的可执行文件中 |
-all_load | 链接器会让所有找到的目标文件都加载到可执行文件中 |
-force_load | 需要指定要进行全部加载的库文件的路径 |
注意:千万不要随便使用 -all_load
这个参数!假如你使用了不止一个静态库,然后又使用了这个参数,那么很有可能会遇到 ld: duplicate symbol
错误,因为不同的库文件里面可能会有相同的目标文件,所以建议在遇到 -ObjC
失效的情况下使用 -force_load
参数。
Bundle 可以理解为一个资源目录,并包含了程序中会用到的资源,如图像、声音、编译好的代码或 nib
文件等。
Bundle 创建有两种方式:
创建一个文件夹,强制重命名该文件夹为 ${yourBundleName}.bundle
。
步骤1:新建一个项目,依次点击 TARGETS → + → mac OS → Framework & Library → Bundle
,输入 Product Name
即建立出 bundle 工程;
步骤2:在 bundle 目录下添加需要的资源文件,编译之后在整个项目工程的 Products 文件夹下得到资源文件 bundle 。
这样做默认情况下 bundle 里面的 png
图片会被转为 tiff
的格式。因此在编译前需要做一步设置:找到 bundle 的工程,把 Build Settings
里的 COMBINE_HIDPI_IMAGES
设置为 NO
之后再编译运行。