Swift

Swift制作Framework,提供给OC项目和Swift项目

2019-02-12  本文已影响34人  代码移动工程师

Swift制作framework

公司的需要需要制作sdk给其他团队用,其实就是framework
简直炸裂!踩了一个又一个的坑!
遍体鳞伤之后,决定一定要记录下来,方便以后自己和有需要的人查阅,能有一点点帮助也是好的

进入正题

官方提供的是.framework.a两种方式制作SDK的方式。
分别对应创建工程时下方的Cocoa Touch FrameworkCocoa 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 -> 创建成功

第二步:基本设置

创建完不急着编写代码,先做一些设置:

  1. 修改最低的系统要求,建议当然低一些好

    image
  2. mach -0 type ,即选择动态库or静态库(甚至Object File)
    想知道这几种type的区别可以移步
    参考浅谈 SDK 开发(一)五种 Mach-O 类型的凛冬之战
    这里我选择默认的Dynamic Library即动态库

    image
  3. 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
  4. Build Active Architecture Only 意思是: 该编译项用于设置是否只编译当前使用的设备对应的arm指令集
    当该选项设置成YES时,你连上一个armv7指令集的设备,就算你的Valid Architectures和Architectures都设置成armv7/armv7s/arm64,还是依然只会生成一个armv7指令集的二进制包
    Release模式为发布模式,需要支持各种设备指令集,所以设置为NO

    image image
  5. Valid Architectures 设置的支持arm指令集。指令集的版本有:armv7/armv7s/arm64。
    假设Architectures设置的支持arm指令集版本只有:arm64时,这时Xcode只会生成一个arm64指令集的二进制包
    所以这里我们都不用改,都包含进来就好了

    image
  6. Dead Code Stripping, 设置为 NO 关闭对代码中“dead”,“unreachable”代码过滤

    image
  7. Link With Standard Libraries 设置为 NO 避免重复链接

    image
  8. Build 环境 设置build环境为release环境下

    image image

第三步:编写代码

在此之前,我们先command+B 看看是否成功

image

build success 并看到Products 下的 文件 XPKit.framework 由红变黑,说明制作成功,右键show in finder

image

这就是我们的framework,只是里面啥都还没写- -。
好了,咱们抓紧写几句,饥渴难耐了吧,但是还是先跟着我来吧,弄明白了再去写自己的代码,少踩好多坑
创建一个Manager类继承自 NSObject

image

写上这么个 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

image

这时候再回来编译,发现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

image
import XPKit

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

image

这里我们能发现我们写了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) 

这里因为我们制作的frameworkdynamic library动态的,所以我们到Project->General->滑到最下方

image

选中 linked frameworks and libraries 中的 XPKit.framework-号 删除,再在上方 Embedded Binaries+号XPKit.framework 加回来,发现上下都有了,如:

image

这时候再command + R Run,就不报错了,并且成功打印:

image
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 + 左键 点到这个文件里去可以看到

image

这时候我们就会发现,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 将两个项目都包含进去

  1. 新建项目或者我们把原先的添加了的framework先丢掉,左上角 File -> save as workspace

    image
  2. 关闭这个.xcodeproj 文件重新打开这个 .xcworkspace

    image
  3. 将我们的 XPKit 项目 拖到 workspace 中,与 Demo-App 并行

    image

    这个时候就会发现,我们就有两个项目了可以分别 build

    image
  4. Demo工程文件 -> General ->embedded binaries 中将 XPKit 下的 .framework 加进来

    image
  5. 试一试,scheme 选择 Demobuild 一下是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 中设置如下:

image

到此为止才是真正完美 ~

第五步:创建通用包

之前说过iphone指令集, 真机与模拟器是不同的,所以编译出来的包也是不通用的,现在我们要做通用的包提供别人使用。

cd /Users/midoo/Desktop/测试文件 

第六步:用Shell脚本创建通用包

创建通用包用到的次数不多,上面的方法够用了,但是如果你还是觉得不方便、很繁琐。那你可以跟我这样做

# 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
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_64i386的包,但是我们的Valid Architectures 中没有。
那么解决问题就方便了,分别添加x86_64i386

image

编译成功~
来到桌面我们发现XPKit.framework,已经静悄悄的在桌面上了,这就是我们的通用包

总结一下

好了,我们总结一下,本篇简单的介绍了一下

  1. 如何用Swift编写,OC项目Swift项目 都能用的 dynamic framework
  2. 如何正确的调试我们的framework
  3. 如何制作通用的 framework 包
为了能让OC项目也能调用,你还记得 @objc 和 public 吗 ^_^

我们的 framework 如果需要导入其他第三方库,该怎么做
本来也想写篇文章,有点懒,大家先可以看看
Swift + framework 的制作(基于pod管理的workspace)

作者:JoeXP
链接:https://www.jianshu.com/p/1ad5bede88bd

上一篇下一篇

猜你喜欢

热点阅读