SPM:Swift Package Manager(swift包管理器),管理Swift代码分发的工具,用于处理模块代码的下载、编译和依赖关系。类似CocoaPods
,不过比CocoaPods
更简洁,代码的侵入性更小,也不需要额外安装工具。
SPM依赖安装
Xcode自带SPM,终端上可查看SPM版本:
$ swift package --version
Swift Package Manager - Swift 5.3.0
新建项目SPMTest
,添加SPM依赖,File
-> Swift Package
-> Add Package Dependency...
或者点击到 PROJECT
-> Swift Packages
点击加号添加依赖
输入需要添加的第三方库的链接:
点击next等待验证成功后直,根据自己的实际需要选择版本,如下图:
有三个选项:
-
Version: 对应库的Release版本, 这里可选择版本规则,它会自动下载这个规则内最新的版本
- Up to Next Major: 指定一个主要版本的范围,例如:5.4.2~6.0.0
- Up to Next Minor: 指定一个小版本范围,例如:5.4.2~5.5.0
- Range: 指定一个版本范围, 例如:5.4.1~5.5.1
- Exact: 指定一个确切的版本,例如:5.4.1
Branch: 直接下载某个分支的代码
Commit: 根据某一次提交记录的 Id下载
添加完成之后,项目中会出现Swift Package Dependencies
这样一个目录:
这样就可以在项目中直接使用这个第三方依赖库了。
如果你要更新SPM中的依赖,选择 File -> Swift Packages -> Update to Latest Package Versions
即可。
如果想要修改某个第三方库的版本策略,可以双击第三方库即可出现修改面板进行相应的修改。
创建本地Swift Package库
我们新建一个Swift Package
,打开我们上面用到的项目SPMTest
后选择File
->New
->Swift package...
,把这个包命名为ZZPackage,并添加到现有的项目中。
新建完成后可以看到在项目工程中包含了ZZPackage
这个Package,
如何引入ZZPackage
到工程中并使用其中的功能模块呢?
Targets
->General
-> Frameworks..
部分,点击+
号,添加ZZPackage
到这里我们就把ZZPackage
引入到我们的项目中了。在ZZPackage
下的 Sources/ZZPackage
目录下新建ExView.swift
,然后在工程中使用到这个文件中的方法:
import SwiftUI
extension View {
//module对外的访问权限设为public
public func printLog(_ value:Any) -> some View {
#if DEBUG
print(value)
#endif
return self
}
}
直接编译报错了:
可以看到要求iOS13及以上,因为我们加入的是swiftUI代码,所以需要在Package.swift
中添加platforms: [.iOS(.v13)],
或在扩展代码上面添加@available(iOS 13.0, *)
。
这两种方式编译都可以成功!
运行看下结果吧:
发布你的 Swift Package
找到SPMTest文件夹下的ZZPackage
文件夹,上传库到云端(github, gitee 或者其他托管服务器)。
然后设置Tag
版本号就可以了。删除本地Package,就可以通过仓库地址加载远程Package了。
发布后远程加载
打tag发布到GitHub后,File > Add Packages
在搜索中输入地址,这个时候可能一直搜索加载报错。这并不是你的包有问题。换一种方式加载,创建本地Package关联项目,然后添加远程依赖项目,这样加载比较快了
- File > New > Package
- 选择把新建的 Swift Package 添加已有的项目中去,Package保存为
Library
- 此时的项目结构如下:
-
Targets
->General
->Frameworks..
部分,点击+
号,添加Library
然后编译一下 - 添加其他远程依赖库,例如添加STNavigationController:
- 它会自动加载依赖,加载成功后就可以在项目中使用依赖的库了
SPM文件及配置
-
Source
文件夹: 第三方库源码位置路径文件 -
Package.swift
: SPM配置文件
我们来看下Package.swift
这个文件:
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "ZZPackage",
platforms: [.iOS(.v13)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "ZZPackage",
targets: ["ZZPackage"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "ZZPackage",
dependencies: []),
.testTarget(
name: "ZZPackageTests",
dependencies: ["ZZPackage"]),
]
)
第一行始终是Swift Tools的版本,这里是swift5.3版本。这行注释说明了构建swift package所需最低的swift版本号。然后我们导入了PackageDescription,这个库提供了我们需要配置swift package所需的API。
- name: 包的项目名称
- platforms: 支持的平台及对应平台的最低版本
- targets: 包含多个target的集合,我们指定target的名字为
ZZPackage
,xcode会自动把Sources/ZZPackage
目录下的所有文件添加到package中。如果你想再新建一个target, 需要在Sources/
目录下新建一个文件夹,然后再targets数组中添加新的target。
.target是PackageDescription.Target
实例类,参数说明:- name: target名字
- dependencies: target的依赖,主要指定Package添加的依赖module的名字
- path: target的路径,如果自定义文件夹需要设置此参数
- exclude: target path中不希望被包含的path
- sources: 资源文件路径
- publicHeadersPath: 公共header文件路径
-
products: 对外公开导出
target
产物,使得其他target
能够使用它们。如果不写会编译报错- .library(
name: "ZZPackage",
type: .static,
targets: ["ZZPackage"]) : 可指定静态库或动态库,默认静态库
- .library(
-
dependencies: 添加包所依赖的其他第三方package包的集合
- .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.0.0"),
- .package(url: "https://github.com/SnapKit/SnapKit.git", from: .init(5, 0, 1)),
- .package(url: "https://github.com/SnapKit/SnapKit.git", from: .init(stringLiteral: "5.0.1")),
- .package(url: "https://github.com/SnapKit/SnapKit.git", Package.Dependency.Requirement.branch("master")), : 指定分支,如果第三方不支持spm的话可以使用这种方式
- .package(path: "../ZZPackage") : 关联本地的SPM库
swiftLanguageVersions: 支持的swift版本
resources 添加资源文件
在.target
下,我们可以添加资源文件字段resources
,例如添加图片资源自定义文件夹imgs
:
如果资源或路径添加不对,编译报错!
对于resources
属性,有两个静态方法: process()
和 copy()
。
- copy会直接拷贝,保存目录结构,可直接copy文件夹
- process是推荐的方式,它所配置的文件会根据具体使用的平台和内置规则进行适当的优化,但好像只能针对单个文件,而不能处理整个文件夹下的资源
//调用举例
public func bgImg() -> some View {
let path = Bundle.module.path(forResource: "imgs/wechat@2x.png", ofType: nil)
guard let uiimage = UIImage.init(contentsOfFile:path!) else {
fatalError("image load path error: \(path as Any)")
}
let img = Image(uiImage: uiimage)
return self
.background(img)
}
在.target
下,我们可以添加资源文件字段resources
,使用默认文件夹Resources
,推荐使用这种:
这种方式可以直接在process
中指定文件夹Resources
,使用的时候也无需引用该路径。swift在编译的时候不会添加Resources路径。
public func bgImgUrl() -> some View {
let path = Bundle.module.url(forResource: "wechat@2x", withExtension: "png")
guard let data = try? Data(contentsOf: path!),
let uiimage = UIImage.init(data: data)
else {
fatalError("image load path error: \(path as Any)")
}
let img = Image(uiImage: uiimage)
return self
.background(img)
}
这里也有一个第三方使用resources
的案例:
在Assets.xcassets
中添加的图片资源不需要使用路径方式,使用方式如下:
Image("imageName", bundle: .module)
SPM不支持混合语言开发,在同一个target中无法使用多语言,否则编译报错。
如何实现混合
可参考这个文章。
这里是个Swift和OC混编的示例,每种语言一个target,单个target内不可使用多语言:
对外导出的.h
头文件,默认放在include
文件中。如果要自定义导出文件需要设置publicHeadersPath
导出的公共头文件夹路径。这里我们把原来的include
文件名改为header
,如下所示:
然后把这个OC的包添加依赖到需要使用的Swift的包里,就可以正常使用了!!