iOS 制作和使用 Framework

介绍

假如你想将你开发的一些控件或者工具与别人分享,一种方法是直接提供源代码文件。然而,这种方法并不是很优雅。因为它会暴露所有的实现细节,而这些实现你可能并不想开源出来。此外,使用者也可能并不想看到你的所有代码,因为他们可能仅仅只是希望将你的这份代码的一部分植入自己的应用中去使用。

网上关于 Framework 制作的教程不说数不胜数吧,但绝对让人眼花缭乱。但很多都步骤过旧也过简单,更没有更新了,所以这里就把我前些时候总结的 制作Framework的过程记录,重新捋一下。

我用的是Xcode 9.4

正题

首先我们需要打开 Xcode,新建一个项目。 并且选择 Cocoa touch Framework。 如下图

然后随便给这个 Framework 取一个名字,比如 TestSDK

为了区分,分别建立了 PublicFile PrivateFile ProjectFile ,

如下图,并分别拖入到对应的 Header

在你弄清楚之前,这三个组的名称可能会让你迷惑,Public 是你期望的,开源出来的,Private 下的头文件依然是可以暴露出来的,因此名字可能有些误导。奇怪的是,在 Project 下的头文件对你的工程来说才是 “私有” 的,因此,你将会更多地希望你的头文件或者在 Public 下,或者在 Project

必须更改的两个注意点:

搜索 mach 将工程改为 静态

搜索 Defines Module 修改为NO

最后你还要更改 SDK最低支持 的版本,这个和项目更改 系统支持 的版本一样,不再赘述

到此基本配置基本完成,也实现了项目的闭源

打包(Bundle)资源

导入Bundle

在ImageViewer中使用Bundle

添加自动打包脚本环境,实现自动打包

总结

知识点

1. 库

库是源代码经过编译,形成的二进制代码,别人项目中使用我们的库的时候,库在参与编译的时候,直接link就OK了,按照link的方式,可以把库分为静态库和动态库

2. 静态库

静态库在编译的时候会被直接拷贝一份,复制到目标程序里,这段代码在目标程序里就不会再改变了。
一般以.a 和 .framework为文件后缀名
这种做法是牺牲应用“体量”来节省编译时间。

3. 动态库

与静态库相反,动态库在编译时并不会被拷贝到目标程序中,目标程序中只会存储指向动态库的引用。等到程序运行时,动态库才会被真正加载进来。
动态库的优点是,不需要拷贝到目标程序中,不会影响目标程序的体积,而且同一份库可以被多个程序使用(因为这个原因,动态库也被称作共享库)。
同时,编译时才载入的特性,也可以让我们随时对库进行替换,而不需要重新编译代码。动态库带来的问题主要是,动态载入会带来一部分性能损失,使用动态库也会使得程序依赖于外部环境。如果环境缺少动态库或者库的版本不正确,就会导致程序无法运行
以.tbd(之前叫.dylib) 和 .framework 为文件后缀名
苹果系统为我们提供了很多动态链接库,我们可以在我们项目工程中查看一下

4. Framework

Framework 是一种打包方式,将库的二进制文件,头文件和有关的资源文件打包到一起,方便管理和分发。
Framework只是一种打包方式,其本身和静态、动态无关!
但Cocoa Touch Framework 的实际内容为 Header + 动态链接库 + 资源文件

对Framework认识的误区

误区①:.framework是动态库,.a是静态库,前面已经讲过,不再赘述
误区②:有人说“自定义的动态库苹果审核不通过”,那我打的framework是不是通不过审核?
任何没有时间前提的结论都是耍流氓!!!
在 iOS 8 / iOS6之前,iOS 平台不支持使用动态 Framework,开发者可以使用的 Framework 只有系统的framework,这种限制可能是出于安全的考虑
换一个角度讲,因为 iOS 应用都是运行在沙盒当中,不同的程序之间不能共享代码,同时动态下载代码又是被苹果明令禁止的,没办法发挥出动态库的优势,实际上动态库也就没有存在的必要了
但是,码农总是喜欢折腾的,一方面骨子里面有一种“你不让我做我偏要做的倔强”,另一方面用framework确实比用.a加头文件的方式简单,所以这一时期的开发者用了很多“奇淫技巧”来制作framework,这就有了Fake Framework 和 Real Framework的区分
在 iOS 8 / iOS6后,iOS平台添加了动态库的支持,同时, Xcode 6 也原生自带了 Framework 支持,注意,前后两个维度的不同是两件事,不要混淆。。。

那么,为什么 iOS 8 要添加动态库的支持?
主要的理由大概就是 Extension 的出现。Extension 和 App 是两个分开的可执行文件,同时需要共享代码,这种情况下动态库的支持就是必不可少的了。但是这种动态 Framework 和系统的 UIKit.Framework 还是有很大区别;还有就是为了支持swift
虽然同样是动态框架,但是和系统 framework 不同,app 中的使用的 Cocoa Touch Framework 在打包和提交 app 时会被放到 app bundle 中(App 和 Extension 的 Bundle 是共享的),运行在沙盒里,而不是系统中。也就是说,不同的 app 就算使用了同样的 framework,但还是会有多份的框架被分别签名,打包和加载,因此苹果又把这种 Framework 称为 Embedded Framework,也正是代码签名机制,通过AppStore发布的APP是无法通过替换服务端下发framework的方式来进行热更新!

总结:

同一个静态库在不同程序中使用时,每一个程序中都得导入一次,打包时也被打包进去,形成一个程序。而动态库在不同程序中,打包时并没有被打包进去,只在程序运行使用时,才链接载入(如系统的框架如UIKit、Foundation等),所以程序体积会小很多,但是苹果不让使用自己的动态库,否则审核就无法通过。这也是为什么前文将工程选择为 静态的原因

常见的错误:

1, 没有改成静态,不支持动态

2, Defines Module 必须改成NO

3, 支持bitcode的frame操作

1
2
3
4
5
6
7
8
9
如果自己想要制作支持 Bitcode 的 Framework,
1, 工程中开启 Enable Bitcode
2, Build Setting 中 Other Linker Flags 中加入 -fembed-bitcode。
3, Build Setting 中 Other C Flags 中加入 -fembed-bitcode。

作者:山神_云网工作室
链接:https://www.jianshu.com/p/ef5a888e57f2
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

同时支持模拟器真机的配置

静态库和自己项目同时开发的方案

上文中会用到自动打包的 shell 脚本,我使用的是公司其他大神写的根据项目需求配置的脚本,这里就不开源了, 大家可以使用以下脚本,也可以实现自动打包

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
#!/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}"