Swift制作framework
公司的需要需要制作sdk给其他团队用,其实就是framework
简直炸裂!踩了一个又一个的坑!
遍体鳞伤之后,决定一定要记录下来,方便以后自己和有需要的人查阅,能有一点点帮助也是好的
进入正题
官方提供的是.framework与.a两种方式制作SDK的方式。
分别对应创建工程时下方的Cocoa Touch Framework和Cocoa Touch Static Library。
.framework与.a两者区别自行百度吧,简书上制作Framework相关的文章基本都有,就懒得copy了。
.framework其实是分动态和静态的,所以.framework即可满足我的要求,想要制作动态或者静态创建framework后可以修改,往后面看。
ps:
在 iOS 8 之前,
iOS 平台不支持开发者使用用户自己的动态 Framework,这种限制可能是出于安全的考虑。
换一个角度讲,因为 iOS 应用都是运行在沙盒当中,不同的程序之间不能共享代码,
同时动态下载代码又是被苹果明令禁止的,没办法发挥出动态库的优势,
实际上动态库也就没有存在的必要了。
但是,iOS 8/Xcode 6 推出之后,
iOS添加了对动态库的支持,为什么 iOS 8 要添加动态库的支持?唯一的理由大概就是Extension 的出现。
Extension 和 App 是两个分开的可执行文件,同时需要共享代码,这种情况下动态库的支持就是必不可少的了。
但是这种动态 Framework 和系统的UIKit.Framework 还是有很大区别。
系统的 Framework 不需要拷贝到目标程序中,我们自己做出来的 Framework 哪怕是动态的,
最后也还是要拷贝到 App 中(App 和Extension 的 Bundle 是共享的),
因此苹果又把这种 Framework 称为 Embedded Framework
手洗干净,挽起袖子干 0_0
第一步:创建Framework工程
运行 XCode -> Cocoa Touch Framework -> 取个名, 语言选择 Swift -> 创建成功
第二步:基本设置
创建完不急着编写代码,先做一些设置:
-
修改最低的系统要求,建议当然低一些好
-
mach -0 type,即选择动态库or静态库(甚至Object File)
想知道这几种type的区别可以移步
参考浅谈 SDK 开发(一)五种 Mach-O 类型的凛冬之战
这里我选择默认的Dynamic Library即动态库
-
Architectures 该编译选项指定了工程将被编译成支持哪些指令集,支持指令集是通过编译生成对应的二进制数据包实现的,如果支持的指令集数目有多个,就会编译出包含多个指令集代码的数据包,造成最终编译的包很大。
那么指令集是什么呢:**iPhone指令集**,苹果处理器支持两个不同的指令集: 32位ARM指令集(armv6|armv7|armv7s)和 64位ARM指令集(arm64),i386|x86_64 是Mac处理器的指令集, i386是针对intel通用微处理器32架构的。 x86_64是针对x86架构的64位处理器。 当使用iOS模拟器的时候会遇到i386|x86_64,iOS模拟器没有arm指令集。所以我们看看我们需要什么指令集
1、debug环境下 设备:arm64(测试机型有限: 6P、5、7 ) 模拟器:iPhone7-Plus:x86_64、iPhone4s:i386 2、release环境下 设备:armv7、arm64 模拟器:i386、x86_64以上所生成的framework均不包含armv7s, 在 Building Setting 中设置一下 Architectures,在原有基础上添加一行 armv7s ,如下:
在原有基础上增加 armv7s
-
Build Active Architecture Only 意思是: 该编译项用于设置是否只编译当前使用的设备对应的arm指令集
当该选项设置成YES时,你连上一个armv7指令集的设备,就算你的Valid Architectures和Architectures都设置成armv7/armv7s/arm64,还是依然只会生成一个armv7指令集的二进制包
Release模式为发布模式,需要支持各种设备指令集,所以设置为NO
-
Valid Architectures 设置的支持arm指令集。指令集的版本有:armv7/armv7s/arm64。
假设Architectures设置的支持arm指令集版本只有:arm64时,这时Xcode只会生成一个arm64指令集的二进制包
所以这里我们都不用改,都包含进来就好了
-
Dead Code Stripping, 设置为 NO 关闭对代码中“dead”,“unreachable”代码过滤
-
Link With Standard Libraries 设置为 NO 避免重复链接
-
Build 环境 设置build环境为
release环境下
第三步:编写代码
在此之前,我们先command+B 看看是否成功

build success 并看到Products 下的 文件 XPKit.framework 由红变黑,说明制作成功,右键show in finder
这就是我们的framework,只是里面啥都还没写- -。
好了,咱们抓紧写几句,饥渴难耐了吧,但是还是先跟着我来吧,弄明白了再去写自己的代码,少踩好多坑
创建一个
Manager类继承自 NSObject
写上这么个
func:
@objc public class XPManager: NSObject {
@objc public func sayHello(){
print("XPKit-->: hello")
}
public func sayWorld() {
print("XPKit-->: world")
}
@objc func saySwift() {
print("XPKit-->: Swift")
}
}
完毕, command + B ,报错了!来看看为啥:
ld: symbol dyld_stub_binder not found (normally in libSystem.dylib). Needed to perform lazy binding to function __T0s23_ContiguousArrayStorageCMa for architecture i386
clang: error: linker command failed with exit code 1 (use -v to see invocation)
这里提示我们少了一个系统库libSystem.dylib,我也不知道为啥,那我们就给加上呗,来到PROJECT->Build Phases->Link Binary With Libraries->+ 加上libSystem.tbd

这时候再回来编译,发现
Build Success。目的达到了 0-0!
第四步:测试写好的Framework
1. 先来测试Swift项目调用swift的framework
先到Products 下的 XPKit.framework 右键 show in finder找到 XPKit.framework
紧接着 create 一个 swift 工程app single view app,并将👆的XPKit.framework拖到工程中,记得勾选 copy if needed

import XPKit
然后 command + 左键 进入,可以看到暴露出来能用的方法

这里我们能发现我们写了
public 修饰的 都暴露出来供app调用
import UIKit
import XPKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let manager = XPManager()
manager.sayHello()
manager.sayWorld()
}
}
command + R Run一下发现报错了
dyld: Library not loaded: @rpath/XPKit.framework/XPKit
Referenced from: /Users/midoo/Library/Developer/CoreSimulator/Devices/8F8ADF9B-D5DD-45A1-B925-03BCBB11D9FF/data/Containers/Bundle/Application/C7D5F85A-DDC8-4BC7-964A-FDC4B5154C5A/SwiftInvokDemo.app/SwiftInvokDemo
Reason: image not found
(lldb)
这里因为我们制作的framework是 dynamic library动态的,所以我们到Project->General->滑到最下方

选中
linked frameworks and libraries 中的 XPKit.framework 点 -号 删除,再在上方 Embedded Binaries 点 +号 将 XPKit.framework 加回来,发现上下都有了,如:
这时候再
command + R Run,就不报错了,并且成功打印:
2. 再来测试Swift项目调用swift的framework
同理 create 一个 OC 的 app,拖进我们的 XPKit.framework 并在 Project->General -> Embedded Binaries 下添加进去
这个时候就要导入#import <XPKit/XPKit-Swift.h> 而不是 #import <XPKit/XPKit.h>
#import <XPKit/XPKit-Swift.h> 是 OC 项目导入 Swift Framework 时自动产生的文件, 给我们展示可以用哪些接口
command + 左键 点到这个文件里去可以看到

这时候我们就会发现,
XPManager 我们的类暴露出来了,但是方法只有一个sayHello()
所以敲黑板,划重点:
我们制作 swift framework 的时候,一定要注意可用性,因为难免会遇到让OC调用的时候。
所以要在暴露在外给人家用的话,一定要写上修饰词 `@objc` 与 `public` 缺一不可
而我们的类,即 Class ,继承了 NSObject 那么即使不写 @objc , 也是OK的,但是属性 与 func一定要写
可以做个测试,在framework工程中写:
public class XPTest: NSObject{
public func sayWorld() {
print("XPKit-->: world")
}
@objc public func sayHello(){
print("XPKit-->: hello")
}
}
重新 build , 注: (每次重新build才会更新framework)
并删除 OC-App 下的 framework 。重新拖到项目中并添加到Embedded Binaries 下
发现,XPKit-Swift.h下暴露的是这样的:
@interface XPTest : NSObject
- (void)sayHello;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
所以如果是 Class 继承了 NSObject 那么即使不写 @objc , 也是OK的,但是属性 与 func一定要写
这里就是简单介绍,还有些细节你们可以慢慢测试
第五步:创建可以直接调试的工程
如果你都跟着步骤写了以后,会发现一直 Build, 一直拖,一直添加,太繁琐。所以咱们可以这样创建一个 workspace 将两个项目都包含进去
-
新建项目或者我们把原先的添加了的framework先丢掉,左上角
File->save as workspace
-
关闭这个
.xcodeproj文件重新打开这个.xcworkspace
-
将我们的
XPKit项目 拖到workspace中,与Demo-App并行
这个时候就会发现,我们就有两个项目了可以分别build了
-
到
Demo中工程文件->General->embedded binaries中将XPKit下的.framework加进来
试一试,
scheme选择Demo,build一下是success,
我们再来到我们的XPManager.h,添加几行代码(当测试)
public class XPHomeViewController: UIViewController{
@objc public let size = CGSize(width: 40, height: 40)
@objc public var point: CGPoint = CGPoint(x: 20, y: 20)
public var content: String?
@objc public var textColor: UIColor?
public override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.red
}
@objc public func sayHello(){
print("XPKit-->: hello")
}
public func sayWorld() {
print("XPKit-->: world")
}
}
当然还是先build一下我们的XPKit,否则Demo里的framework不更新呐,再到OC项目中import并点进去看看。
注:我这里试了几次总是没有自动补全,那就自己手写 import地址吧
#import <XPKit/XPKit-Swift.h>
点进去发现
@interface XPHomeViewController : UIViewController
@property (nonatomic, readonly) CGSize size;
@property (nonatomic) CGPoint point;
@property (nonatomic, strong) UIColor * _Nullable textColor;
- (void)viewDidLoad;
- (void)sayHello;
- (nonnull instancetype)initWithNibName:(NSString * _Nullable)nibNameOrNil bundle:(NSBundle * _Nullable)nibBundleOrNil OBJC_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)aDecoder OBJC_DESIGNATED_INITIALIZER;
@end
SWIFT_CLASS("_TtC5XPKit9XPManager")
@interface XPManager : NSObject
- (void)sayHello;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
SWIFT_CLASS("_TtC5XPKit6XPTest")
@interface XPTest : NSObject
- (void)sayHello;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
哇,完美,并且,可以直接在项目里用。那么边写边测,还能设断点,完美~跟平时写app没两样,其实还差最后一步 >_<.
当你调用并且运行,发现又报错了
dyld: Library not loaded: @rpath/libswiftCore.dylib
Referenced from: /Users/midoo/Library/Developer/Xcode/DerivedData/OCInvokDemo-fvmdsbbzuqqnjnbswwgpcidptord/Build/Products/Debug-iphonesimulator/XPKit.framework/XPKit
Reason: image not found
并且这次我们已经加到 Embedded Binaries 中了,原因是,如果我们在OC项目中引用swift framework,还需要到 build setting 中设置如下:

到此为止才是真正完美 ~
第五步:创建通用包
之前说过iphone指令集, 真机与模拟器是不同的,所以编译出来的包也是不通用的,现在我们要做通用的包提供别人使用。
- 切换到
XPKit, 选择任意一个模拟器build一下, - 再选择
Generic iOS Device,build一下 -
show in finder,我们只看release包的,不要关闭finder
- 我们需要用到终端来创建通用包,打开我们的终端
- 先
cd到一个文件目录下,为了存放我们制作的包
cd /Users/midoo/Desktop/测试文件
- 输入
lipo -create空格以后,分别到release-iphoneos和release-iphonesimulator下的XPKit.framework-> 选择XPKit二进制文件,拖动到 终端下
- 紧接在后面写上
-output+你的包名,回车
注:我选择的文件夹下面有XPKit文件名,重复了 - -。创建失败...所以换了个文件夹
- OK,这个时候还没完,只是创建了一个通用的二进制文件,还得把其他东西给他加上,回到
release-iphoneos和release-iphonesimulator,选择任意一个文件夹,整个复制出来 -
并将制作好的二进制文件拖出来 替换掉
image.png -
还没完!!!再到另一个文件夹,将指令集文件copy到这个,我们这个新的文件夹下
保证这些都包含在内,那么这个framework包才算制作完成
- 到此为止我们得到了我们想要的通用包,按照相同的方法,拖到工程中,引入到
Embedded Binaries,可以调试了
第六步:用Shell脚本创建通用包
创建通用包用到的次数不多,上面的方法够用了,但是如果你还是觉得不方便、很繁琐。那你可以跟我这样做
- 选择
XPKit工程点击左下角+
- 创建一个
Aggregate。去个名字,类似CommonBuilder
image.png - 选中
CommonBuilder->Build Phases-> 添加New Run Script Phase
- 在编辑器内输入我们的脚本代码,请全部复制,黏贴,记得修改第二步引号内的内容为你的
framework name
# Merge Script
# 1
# Set bash script to exit immediately if any commands fail.
set -e
# 2
# Setup some constants for use later on.
FRAMEWORK_NAME="Your framework name"
# 3
# If remnants from a previous build exist, delete them.
if [ -d "${SRCROOT}/build" ]; then
rm -rf "${SRCROOT}/build"
fi
# 4
# Build the framework for device and for simulator (using
# all needed architectures).
xcodebuild -target "${FRAMEWORK_NAME}" -configuration Release -arch arm64 -arch armv7 -arch armv7s only_active_arch=no defines_module=yes -sdk "iphoneos"
xcodebuild -target "${FRAMEWORK_NAME}" -configuration Release -arch x86_64 -arch i386 only_active_arch=no defines_module=yes -sdk "iphonesimulator"
# 5
# Remove .framework file if exists on Desktop from previous run.
if [ -d "${HOME}/Desktop/${FRAMEWORK_NAME}.framework" ]; then
rm -rf "${HOME}/Desktop/${FRAMEWORK_NAME}.framework"
fi
# 6
# Copy the device version of framework to Desktop.
cp -r "${SRCROOT}/build/Release-iphoneos/${FRAMEWORK_NAME}.framework" "${HOME}/Desktop/${FRAMEWORK_NAME}.framework"
# 7
# Replace the framework executable within the framework with
# a new version created by merging the device and simulator
# frameworks' executables with lipo.
lipo -create -output "${HOME}/Desktop/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${SRCROOT}/build/Release-iphoneos/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}" "${SRCROOT}/build/Release-iphonesimulator/${FRAMEWORK_NAME}.framework/${FRAMEWORK_NAME}"
# 8
# Copy the Swift module mappings for the simulator into the
# framework. The device mappings already exist from step 6.
cp -r "${SRCROOT}/build/Release-iphonesimulator/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule/" "${HOME}/Desktop/${FRAMEWORK_NAME}.framework/Modules/${FRAMEWORK_NAME}.swiftmodule"
# 9
# Delete the most recent build.
if [ -d "${SRCROOT}/build" ]; then
rm -rf "${SRCROOT}/build"
fi
如图:

-
scheme选择CommonBuilder,任意模拟器,编译,报错了看看报了什么错
Command /bin/sh failed with exit code 65
你们以后看到这些不用慌,网上看,信息都在上面
=== BUILD TARGET XPKit OF PROJECT XPKit WITH CONFIGURATION Release ===
Check dependencies
No architectures to compile for (ARCHS=x86_64 i386, VALID_ARCHS=arm64 armv7 armv7s).
** BUILD FAILED **
分析一下,这里都是我们提到过的指令集。 VALID_ARCHS=arm64 armv7 armv7s 这就是我们开始在 Build Setting ->Valid Architectures 中设置的内容,很明显,意思是脚本里,要制作包含 x86_64 和 i386的包,但是我们的Valid Architectures 中没有。
那么解决问题就方便了,分别添加x86_64 和 i386

编译成功~
来到桌面我们发现
XPKit.framework,已经静悄悄的在桌面上了,这就是我们的通用包
总结一下
好了,我们总结一下,本篇简单的介绍了一下
- 如何用
Swift编写,OC项目与Swift项目都能用的dynamic framework - 如何正确的调试我们的
framework - 如何制作通用的
framework 包
为了能让OC项目也能调用,你还记得 @objc 和 public 吗 ^_^
我们的 framework 如果需要导入其他第三方库,该怎么做
本来也想写篇文章,有点懒,大家先可以看看
Swift + framework 的制作(基于pod管理的workspace)



























