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
-> 创建成功
第二步:基本设置
创建完不急着编写代码,先做一些设置:
-
修改最低的系统要求,建议当然低一些好
image -
imagemach -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 ,如下:
image在原有基础上增加 armv7s
image -
Build Active Architecture Only 意思是: 该编译项用于设置是否只编译当前使用的设备对应的arm指令集
image image
当该选项设置成YES时,你连上一个armv7指令集的设备,就算你的Valid Architectures和Architectures都设置成armv7/armv7s/arm64,还是依然只会生成一个armv7指令集的二进制包
Release模式为发布模式,需要支持各种设备指令集,所以设置为NO -
Valid Architectures 设置的支持arm指令集。指令集的版本有:armv7/armv7s/arm64。
image
假设Architectures设置的支持arm指令集版本只有:arm64时,这时Xcode只会生成一个arm64指令集的二进制包
所以这里我们都不用改,都包含进来就好了 -
Dead Code Stripping, 设置为 NO 关闭对代码中“dead”,“unreachable”代码过滤
image -
Link With Standard Libraries 设置为 NO 避免重复链接
image -
Build 环境 设置build环境为
image imagerelease
环境下
第三步:编写代码
在此之前,我们先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先丢掉,左上角
imageFile
->save as workspace
-
关闭这个
image.xcodeproj
文件重新打开这个.xcworkspace
-
将我们的
imageXPKit
项目 拖到workspace
中,与Demo-App
并行这个时候就会发现,我们就有两个项目了可以分别
imagebuild
了 -
到
imageDemo
中工程文件
->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
一下 -
imageshow in finder
,我们只看release
包的,不要关闭finder
-
我们需要用到终端来创建通用包,打开我们的终端
-
先
cd
到一个文件目录下,为了存放我们制作的包
cd /Users/midoo/Desktop/测试文件
-
输入
image imagelipo -create
空格以后,分别到release-iphoneos
和release-iphonesimulator
下的XPKit.framework
-> 选择XPKit
二进制文件,拖动到 终端下 -
紧接在后面写上
image-output
+你的包名
,回车
注:我选择的文件夹下面有XPKit
文件名,重复了 - -。创建失败...所以换了个文件夹 -
OK,这个时候还没完,只是创建了一个通用的二进制文件,还得把其他东西给他加上,回到
release-iphoneos
和release-iphonesimulator
,选择任意一个文件夹,整个复制出来 -
并将制作好的二进制文件拖出来 替换掉
image -
还没完!!!再到另一个文件夹,将指令集文件copy到这个,我们这个新的文件夹下
image保证这些都包含在内,那么这个framework包才算制作完成
image -
到此为止我们得到了我们想要的通用包,按照相同的方法,拖到工程中,引入到
imageEmbedded Binaries
,可以调试了
第六步:用Shell脚本创建通用包
创建通用包用到的次数不多,上面的方法够用了,但是如果你还是觉得不方便、很繁琐。那你可以跟我这样做
-
选择
imageXPKit
工程点击左下角+
-
创建一个
imageAggregate
。去个名字,类似CommonBuilder
-
选中
imageCommonBuilder
->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
如图:
image-
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)
作者:JoeXP
链接:https://www.jianshu.com/p/1ad5bede88bd
网友评论