Swift Package Manager
(Swift
包管理器,一般简称 SwiftPM
或 SPM
)是苹果官方提供的一个用于管理源代码分发的工具,用于管理Swift
代码分发的工具。它与Swift
构建系统集成在一起,可以自动执行依赖项的下载,编译和链接过程。该工具可以帮助我们编译和链接 Swift packages
(包),管理依赖关系、版本控制,以及支持灵活分发和协作(公开、私有、团队共享)等。支持Swift
、Objective-C
、Objective-C ++
、C
或C ++
。
SPM
软件包管理器包含在Swift 3.0
及更高版本中,用于处理模块代码的下载、编译和依赖关系等,支持 macOS
和 Linux
。与CocoaPods
和Carthage
功能类似,不过比这两个更简洁,代码的侵入性更小,也不需要额外安装工具。
SPM版本
Xcode
自带,可以在终端查看版本
~ swift package --version
Swift Package Manager - Swift 5.3.0
SPM管理添加三方库
SPM
包含在Xcode 8.0
和所有后续版本中。在Xcode 11
发布的时候,通过libSwiftPM
集成来支持iOS
、watchOS
和tvOS
平台。也就是iOS11
以后才能用于iOS
、watchOS
和tvOS
平台。并且需要库适配SPM
。
⚠️:也就是Xcode
版本11
,iOS
最低版本支持iOS8
,macOS 10.10
,tvOS 9.0
,watchOS 2.0
,当然也支持Linux
。
如何判断一个库支不支持SPM
?
查看库是否有Package.swift
文件。
方式一:File -> Swift Packages -> Add Package Dependency
方式二:PROJECT -> Swift Packages -> Packages
两种方式最终会打开
Choose Package Repository
:1.输入三方库链接,验证通过后界面如下:
可以通过3种方式集成Version
、Branch
以及Commit
。
Version
有4个选项:
-
Up to Next Major
: 大版本更新 当前指定的版本号到下一个大版本号之间的最新版本,例如5.0.0 ~ 6.0.0
(不包含6.0.0
) -
Up to Next Minor
: 小版本更新 当前指定的版本号到下一个次版本号之间的最新版本,例如5.0.0 ~ 5.1.0
(不包含5.1.0
) -
Up to Next Ranger
: 指定的两个版本号之间的最新版本,例如5.1.0 ~ 5.2.2
(不包含5.2.2
) -
Up to Next Exact
: 指定使用某一具体的版本号
2.完成后
3.管理/更新依赖
-
Reset Package Caches
:重置依赖包的缓存。 -
Resolve Package Versions
:生成Package.resolved
文件,确定依赖的版本信息。 -
Update to Latest Package Versions
:根据配置的依赖包版本控制规则,对依赖包进行升级。
4.使用
这个时候
Target
种已经引入进来了。至此,整个依赖流程就已经处理完了。
SPM是如何管理RXSwift
的呢?
在引进RXSwift
库后在SPM
项目中并没有相关代码,也没有类似CocoaPods
的Podfile
文件,在项目的RxSwift
右键Show In Finder
查看下具体位置:
/Users/binxiao/Library/Developer/Xcode/DerivedData/SPM-eahnayohsullgthbkkmzyjocrsnv/SourcePackages/checkouts/RxSwift
也就是Xcode
项目编译缓存DerivedData
中。
那么配置文件在哪里呢?
直接去
SPM.xcodeproj
中找,在project.pbxproj
中搜索RXSwift
就能找到配置信息了。这也就是SPM
存放配置信息的地方。CocoaPods与SPM
能否一起使用?
能够同时使用:
生成Swift Package
初始化一个Swift Package
项目有两种方式:命令行和Xcode
命令行中的SPM
swift package init
:
➜ SwiftPackageByCommandLine swift package init
Creating library package: SwiftPackageByCommandLine
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/SwiftPackageByCommandLine/SwiftPackageByCommandLine.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/SwiftPackageByCommandLineTests/
Creating Tests/SwiftPackageByCommandLineTests/SwiftPackageByCommandLineTests.swift
Creating Tests/SwiftPackageByCommandLineTests/XCTestManifests.swift
目录结构如下:
SPM
管理的每个 Package
相当于 Xcode.Project
。目录下必须含有 Package.swift
和 Sources
代码文件夹(链接系统的包除外)
-
Package.swift
:描述库的配置和属性,相当于CocoaPods
的.podspec
和.podfile
文件。 -
Source/TargetName
:存放源码,TargetName
用来区分Target
。
swift package 命令
swift package init --help
OVERVIEW: Initialize a new package
OPTIONS:
--name Provide custom package name
--type empty|library|executable|system-module|manifest
--name
指定Package
名称 ,默认上一级文件夹名称。
--type:
-
empty
:空包
Source
文件夹下什么都没有,也不能编译 -
library
:静态包
Source
文件夹下有个和包同名.swift
文件,里面有个空结构体
struct library {
var text = "Hello, World!"
}
-
executable
: 可执行包(默认)
Source
文件夹下有个main.swift
文件,在build
之后会在.build/debug/
目录下生成一个可执行文件,可以通过swift run
或者直接点击运行,启动一个进程。
➜ SwiftPackage cd executable
➜ executable swift package init --type executable
Creating executable package: executable
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/executable/main.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/executableTests/
Creating Tests/executableTests/executableTests.swift
Creating Tests/executableTests/XCTestManifests.swift
➜ executable swift build
[3/3] Linking executable
➜ executable swift run
Hello, world!
-
system-module
:系统包
这种包是专门为了链接系统库(例如libgit
、jpeglib
、mysql
这种系统库)准备的,本身不需要任何代码,所以也没有Source
文件夹,但是需要编辑module.modulemap
文件去查找系统库路径 (Swift 4.2
已经被其他方式取代) -
manifest
:只包含一个Package.swift
文件。
其它命令用法可以通过swift package --help
查看。
swift build
swift build
用于编译package
,SPM
使用llbuild
作为其底层编译引擎,提供快速和正确的增量编译,它也被 Xcode
新的编译建系统使用,是 Swift
开源项目的一部分。
SPM
构建 package
的编译环境是在一个沙盒中被隔离独立的,无法随意执行命令或 shell
脚本,因此保证了其安全性。同时也支持基于 XE.framework
单元测试,并发测试,或者指定测试某些场景(Test Filtering
)等。
swift run
swift run
: 用于编译并运行一个可执行文件,该命令是在 Swift 4
中新增加的,相当于:
$ swift build
$ .build/debug/executable(可执行文件)
swift test
swift test
: 用于运行 package
中的单元测试
Xcode中的SPM
File -> New -> Swift Package
(shift + control + command + N
)
或者:
File -> New -> Project
(command + shift + n
)修改SwiftPackageByXcode.swift
如下:
public struct SwiftPackageByXcode {
public init() {
}
public var text = "Hello, World!"
public func test() {
print(self.text)
}
}
⚠️:Xcode验证Swift Package
需要打开Package.swift
编译看是否通过。
宿主工程依赖本地Package(以SPM
为例):
直接将整个Package
拖入宿主工程,然后在Frameworks
添加(目前没有找到类似CocoaPods
直接:path => ''
指向本地的方式):
测试编译运行通过
引用远程依赖
上传项目至github
(记的打tag
)
引入到之前的SPM
工程中:
调用
SwiftPackageByXcode
的test
方法进行验证:
//ViewController.swift
import UIKit
import RxSwift
import HandyJSON
import SwiftPackageByXcode
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
SwiftPackageByXcode().test()
}
}
输出:
Hello, World!
至此生成自己的SPM
库并且使用就已经完成了。
SPM-OC
生成Swift Package for OC
直接创建Swift Package
,将默认Sources/PackageName
目录下的.Swift
文件删除,加入C
,C++
以及OC
的代码,Package
目录结构如下:
如果头文件不想放在
include
目录下,要么配置publicHeadersPath
要么进行软连接。
软连接:进入
include
目录,终端执行ln -s
。可以写一个脚本进行维护。//进入include目录,终端执行 ln -s ../test.h test.h
Package
配置:
name: "SPMLibraryForOC",
products: [
.library(
name: "SPMLibraryForOC",
// type: .static,
targets: ["SPMLibraryForOC"]
),
],
targets: [
.target(
name: "SPMLibraryForOC"
//源文件路径,默认Sources/TargetName
// path: "Sources/SPMLibraryForOC"
//默认路径 path/include
// : "Sources/SPMLibraryForOC/include"
)
]
如果对已经存在的库进行适配,需要自己根据目录配置path
(默认Sources/TargetName
)以及publicHeadersPath
(默认path/include
)。
具体的配置可以参考一些开源的第三方OC
库的适配,比如:AFNetworking
使用
添加Swift Package
依赖,桥接文件(XXX-Bridging-Header.h
)中导入头文件:
#import <AFNetworking.h>
#import <SPMTestC.h>
#import <SPMTestMixed.h>
#import <SPMTestOC.h>
调用:
import RxSwift
import HandyJSON
import SwiftPackageByXcode
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//调用三方 Swift Package
let rxError = RxError.argumentOutOfRange
print("RxSwift \(rxError)")
//调用自定义Swift Package
print("SwiftPackageByXcode")
SwiftPackageByXcode().test()
//调用三方 Swift Package OC
let manager = AFHTTPSessionManager(baseURL: URL(string: "https:www.baidu.com"))
print("AFNetworking \(manager)")
//调用自定义Swift Package OC/C/C++
testC()
SPMTestOC().testOC()
SPMTestMixed().testCplus()
}
}
输出:
RxSwift Argument out of range.
SwiftPackageByXcode
Hello, World!
AFNetworking <AFHTTPSessionManager: 0x600003a480f0, baseURL: https:www.baidu.com, session: <__NSURLSessionLocal: 0x7fe71b008d90>, operationQueue: <NSOperationQueue: 0x7fe71b008b90>{name = 'NSOperationQueue 0x7fe71b008b90'}>
test c function
2021-03-16 17:06:30.362943+0800 SPM[65233:17898750] test oc function
2021-03-16 17:06:30.363060+0800 SPM[65233:17898750] testCplus function
testCplusHotpotCat
库中依赖其他库
需要在Package.swift
文件中添加依赖,直接在dependencies
中加入保存Xcode
就会自动去拉取了。
//依赖库配置
dependencies: [
//第三方有规范的格式: 大版本.小版本.测试版本
.package(url: "https://gitee.com/guaizaizaiguai/Alamofire.git",
from: .init(5, 4, 1)
)
],
Package.swift配置
Package.dependencies
Package.dependencies
:用于添加包的依赖。在执行 Swift build
时会自动执行一个 swift package resolve
命令,该命令会解析 Package.swift
的依赖,并生成对应的 package.resolved
文件。类似于CocoaPods
中的 Podfile.lock
或 Carthage
中的 Cartfile.resolved
。当执行依赖解析的时候,会优先解析这个文件,不存在时才会解析 Package.swift
。在上传时把它忽略掉。
主工程 package.resolved
如果主工程依赖了Swift Package
那么package.resolved
目录在项目目录/项目名称.xcodeproj/project.workspace/xcshareddata/swiftpm/
⚠️:如果项目引入了
Cocoapods
,目录为:项目目录/项目名.xcworkspace/xcshareddata/swiftpm
内容如下:
{
"object": {
"pins": [
{
"package": "AFNetworking",
"repositoryURL": "https://gitee.com/guaizaizaiguai/AFNetworking",
"state": {
"branch": null,
"revision": "ffae2391ab0c29dc88eb0a58d2f5b2c2c27cadbf",
"version": "4.0.1"
}
},
{
"package": "RxSwift",
"repositoryURL": "https://gitee.com/mirrors/RxSwift",
"state": {
"branch": null,
"revision": "7e01c05f25c025143073eaa3be3532f9375c614b",
"version": "6.1.0"
}
},
{
"package": "SPMLibraryForOC",
"repositoryURL": "https://gitee.com/guaizaizaiguai/SPMLibraryForOC.git",
"state": {
"branch": "master",
"revision": "d96ac816a5ddf4732d29f8ffadf2d3e52a99b1b2",
"version": null
}
},
{
"package": "SwiftPackageByXcode",
"repositoryURL": "https://gitee.com/guaizaizaiguai/SwiftPackageByXcode.git",
"state": {
"branch": "master",
"revision": "80993a21fd03b0334e0e5a25fe29d04d45d7e472",
"version": null
}
}
]
},
"version": 1
}
自己 Package 的 package.resolved
如果自己的Swift Package
依赖了其它Package
那么也会在根目录生成Package.resolved
,里面记录依赖库的版本信息。
依赖其它Swift Package(5个类型)
详细信息可以在Package.Dependency
中查看。
name
参数(库名称)一般都省略(在库名称和URL中地址相同的情况下,官方文档这样描述:The name of the package, or nil to deduce it from the URL.
)
git source + 确定版本号
//指定版本号
.package(url: "https://gitee.com/guaizaizaiguai/Alamofire.git",
.exact("5.4.1")
),
//以下这两种方式也可以,如果版本错误会往上找5.4.1~6.0.0
.package(url: "https://gitee.com/guaizaizaiguai/Alamofire.git",from: "5.4.1"),
//这种方式是库名称和URL中的Alamofire.git不匹配。如果匹配会在URL中查找。 The name of the package, or nil to deduce it from the URL.
.package(name: "Alamofire", url: "https://gitee.com/guaizaizaiguai/Alamofire.git", from: "5.4.1")
//第三方有规范的格式: 大版本.小版本.测试版本
.package(url: "https://gitee.com/guaizaizaiguai/Alamofire.git",
from: .init(5, 4, 1)
),
//如果版本不太规范比如只有两位版本号的库
.package(url: "https://gitee.com/guaizaizaiguai/Alamofire.git",
from: .init(stringLiteral: "5.4")
),
-
git source + 版本区间
枚举值在Package.Dependency.Requirement
中定义
//指定5.4.1~6.0.0
.package(url: "https://gitee.com/guaizaizaiguai/Alamofire.git",
.upToNextMajor(from: "5.4.1")
),
//指定5.4.1~5.5.0
.package(url: "https://gitee.com/guaizaizaiguai/Alamofire.git",
.upToNextMinor(from: "5.4.1")
),
//指定区间 5.0.1~5.5.6
//.package(url: "https://gitee.com/guaizaizaiguai/Alamofire.git", "5.0.1"..."5.5.6"),
.package(url: "https://gitee.com/guaizaizaiguai/Alamofire.git", "5.0.1"..<"5.5.6"),
git source + commit
//指定commit
.package(url: "https://gitee.com/guaizaizaiguai/Alamofire.git",
.revision("commit")
),
-git source + 分支
//指定分支
.package(url: "https://gitee.com/guaizaizaiguai/Alamofire.git",
.branch("master")
),
本地路径
.package(path: "../Example"),
条件依赖
以前可以在.package
中指定条件,目前好像已经去除了,可以在Target
中分别配置了。
//⚠️目前已经没有这个的配置了
.package(url: "https://..",majorVersion:1),
.package(url: "https://...", from: "1.0.0", when: .testing),
.package(url: "https://...", from: "2.0.0", when: .os(.linux),
平台配置(Package.SupportedPlatform)
这个 Struct
用于设置包的依赖平台和版本:
//平台配置
platforms: [
.iOS(.v10), //枚举定义在 IOSVersion 中
.macOS(.v10_12),// MacOSVersion
.watchOS(.v5),//WatchOSVersion
.tvOS(.v10),//TVOSVersion
//也可以直接指定字符串版本号
//.macOS("10.15"),
//.iOS("13")
],
具体版本可以分别在IOSVersion
,MacOSVersion
,WatchOSVersion
,TVOSVersion
中查看。
⚠️:虽然这个属性是个数组,同一平台只允许设置1个值,否则报错:
//平台配置
platforms: [
.iOS(.v10),
.iOS(.v13)
],
//Failed to parse the manifest file
//found multiple declaration for the platform: ios
Package.Product
Product
是 Package
编译后对外的产物,当执行完 Swift build
之后,就会在 .build/debug
下生成对应的可执行文件/静态库 (.a
)/动态库( .dylib
):
- 可执行文件
创建Package
指定type
为executable
。
//库配置
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.executable(
name: "SwiftPackageByXcode",
targets: ["SwiftPackageByXcode"]),
],
- 静态库或者动态库
//库配置
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "SwiftPackageByXcode",
//动态库 or 静态库(默认)
type: .static, //.dynamic
targets: ["SwiftPackageByXcode"]),
],
参数说明
-
name
:package
导出产物的名称,也就别的地方引用import PackageName
。 -
executable
:可执行文件 -
library
:库文件 -
targets
:包含的target
-
type
:LibraryType
分为static
和dynamic
Package.Target
target
是 Package
的基本构件,和 xcodeproject
一样,Package
可以有多个 target。
一般分为三种类型:
- 常规
Target
- 测试
Target
- 系统库
Target
分别对应
/// The type of this target.
public enum TargetType : String, Encodable {
case regular
case test
case system
case binary
}
public static func target(name: String, dependencies: [PackageDescription.Target.Dependency] = [], path: String? = nil, exclude: [String] = [], sources: [String]? = nil, publicHeadersPath: String? = nil, cSettings: [PackageDescription.CSetting]? = nil, cxxSettings: [PackageDescription.CXXSetting]? = nil, swiftSettings: [PackageDescription.SwiftSetting]? = nil, linkerSettings: [PackageDescription.LinkerSetting]? = nil) -> PackageDescription.Target
public static func testTarget(name: String, dependencies: [PackageDescription.Target.Dependency] = [], path: String? = nil, exclude: [String] = [], sources: [String]? = nil, cSettings: [PackageDescription.CSetting]? = nil, cxxSettings: [PackageDescription.CXXSetting]? = nil, swiftSettings: [PackageDescription.SwiftSetting]? = nil, linkerSettings: [PackageDescription.LinkerSetting]? = nil) -> PackageDescription.Target
public static func systemLibrary(name: String, path: String? = nil, pkgConfig: String? = nil, providers: [PackageDescription.SystemPackageProvider]? = nil) -> PackageDescription.Target=
Target配置
-
name
:名称。 -
dependencies
:依赖项
这里的dependencies
与Package.Dependency
不是一个东西。这里可以依赖
Package.Dependency
中的内容或者依赖另一个target
这里只需要写Package
或者Target
的名字字符串(Target.Dependency
这个枚举也实现了ExpressibleByStringLiteral
)。
//导入依赖
dependencies: [
"Alamofire",
.byName(name: "Alamofire"),
.target(name: "Alamofire"),
.product(name: "Alamofire", package: "Alamofire"),
.target(name: "Alamofire", condition: .when(platforms: [.iOS]))
],
-
path
:target
的路径,默认是[PackageRoot]/Sources/[TargetName]
。 -
sources
:源文件路径。默认TargetName
文件夹下都是源代码文件,会递归搜索。
SPM
会自动包含磁盘上当前package
中Sources
目录中的源文件,且Sources
目录下的各子文件夹中的代码会自动与同名的target
关联,而不需要在清单文件中显式声明。 -
resources
:资源文件。 -
exclude
: 需要被排除在外的文件/文件夹,这些文件不会参与编译。 -
publicHeadersPath
:c
家族库的公共头文件地址。 -
swiftSettings
:定义一个用于特定环境(例如Debug
)的宏。
swiftSettings:[
.define("ENABLE_SOMETHING", .when(configuration: .release)),
.define("ENABLE_OTHERTHING", .when(platforms: [.iOS], configuration: .release)),
.define("ENABLE_OTHERTHING", .when(configuration: .debug)),
.unsafeFlags(["-cross-module-optimization"]),
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
]
define
:条件编译,相当于
#if ENABLE_SOMETHING
...
#endif
unsafeFlags
:设置的 Swift package
不能用作依赖项。
linkerSettings: [
.unsafeFlags(["-Xlinker", "-rpath", "-Xlinker", "@executable_path/../../../lib/swift/macosx"], .when(platforms: [.macOS])),
]),
-
cSettings
:c
家族语言设置
cSettings: [
.headerSearchPath("path/relative/to/my/target"),
.define("DISABLE_SOMETHING", .when(platforms: [.iOS], configuration: .release)),
],
-
linkerSettings
: 用于链接一些系统库。比如:
.target(
name: "OpenGLLibrary",
dependencies: ["Cglew", "Cglfw"],
linkerSettings: [
.linkedFramework("OpenGL")
]),
-
cxxSettings
: A CXX-language build setting,定义在CXXSetting
中。
let versionStr = "10.3.0"
cxxSettings:[
.headerSearchPath("aztec"),
.define("REALM_DEBUG", .when(configuration: .debug)),
.define("REALM_VERSION_MAJOR", to: String(versionStr.split(separator: "-")[0].split(separator: ".")[0]))
]
-
systemLibrary
:导入系统库
GLFW
和GLEW
都是由C
编写的库,其中Cglew
与Cglfw
是制作的可以在Swift
中调用的模块。这里首先需要通过Homebrew
在macOS
上按安装Cglew
和Cglfw
。
...
targets: [
....
.systemLibrary(
name: "Cglew",
pkgConfig: "glew",
providers: [
.brew(["glew"])
]),
.systemLibrary(
name: "Cglfw",
pkgConfig: "glfw3",
providers: [
.brew(["glfw"])
]),
]
其中SystemPackageProvider
可以选brew
、apt
、yum
配置完了后还要创建对应的Target
目录和头文件以及module.modulemap
文件。
-
providers
:是可选的,在目标库没有被安装时,它为SPM
提供了用于安装库的方式的提示。 -
pkConfig
:指定pkConfig
文件的名称SPM
可以通过它找到要导入的库的头文件和库搜索路径pkConfig
的名称可以在库的安装路径的lib/pkconfig/xxx.pc
中找到。
⚠️:目前SPM
支持编译其他语言,如:C
/C++
/Objective-C
等,但是目前不支持将这些语言与Swift
混合编译放在同一个target
中,需要分别放在不同的target
。
swiftLanguageVersions
支持的Swift
语言版本,可以同时设置多个版本。
//支持的 Swift 语言版本
swiftLanguageVersions: [
.v5
]
cLanguageStandard
用于package
中所有C
目标的C
语言标准,具体定义在CLanguageStandard
中,
cLanguageStandard:.c11,
cxxLanguageStandard
用于package
中所有C++
目标的C++
语言标准,具体定义在CXXLanguageStandard
中。
cxxLanguageStandard: .cxx14
添加资源文件
SPM
令人诟病的一个问题就是无法在包里添加资源文件。具体可以观看官方视频
配置
对于一些使用目的明确的文件类型,比如下面图中的这些。开发者不需要在 Package.swift
文件中配置任何东西,因为 Xcode
知道这些类型的文件是代表什么,比如 .xcassets
文件代表图片、颜色资源, xib
代表用户界面文件等。
对于一些使用目的不太明确的文件类型(如下图中的一些文件类型),则需要在
package.swift
文件中配置。例如纯文本文件,这种文件中的数据可能是需要在运行时被加载而计算或者展示,也可能只是一个开发者文档。对于意义不明的文件,需要在
package.swift
中根据规则配置:targets: [
//自己库的target
.target(
name: "SwiftPackageByXcode",
//导入依赖
dependencies: [
"Alamofire",
],
//排除
exclude:[
"Readme.txt"
],
//资源
resources:[
.process("image.png"),
.copy("BundleData")
]),
],
-
Media.xcasset
和Test.storyboard
文件,Xcode
能明确知道它代表什么,所以不需要在这个配置文件中配置 -
Readme.txt
文件是说明文件,写在target
的exclude
属性中,Xcode
就不会把它编译进包里 -
image.png
和BundleData
不能自动识别的类型并且需要被加载到package
中则配置在resources
属性中。
对于resource
属性,静态方法:process()
和copy()
。process()
是推荐的方式,它所配置的文件会根据具体使用的平台和内置规则进行适当的优化。比如在运行时将storyboard
或者asset catalog
转换成适当的形式,也包括压缩图片等。如果文件类型无法识别,或者不能根据平台做任何优化,就只会被简单的拷贝(copy()
,目录的复制会递归进行深复制
)。
1.对于不需要被外部引用的,例如内部的开发者文档
README
,需要配置在target.excludes
属性中。
2.对于运行时有用到,可以被系统根据平台优化的文件,比如各种图片,需要配置在target.resource.process
属性里
3.对于运行时有用到,不存在优化的文件,比如各种图片,需要配置在target.resource.copy
属性里
构建
当一个 App
使用 package
时,这个 package
包括源文件和资源文件。在编译时首先会将 Package
中每个 target
的源文件编译成 module
链接到 App
中,然后这些 target
中的资源文件则会被加工成 bundle
放到这些 module
中。
在
Apple
平台中,App
和App extension
都是bundle
集合,这些package
的bundle
就是App
的一部分,所以不需要做其他处理,就能在运行时获取这些bundle
。
当被编译到一个unbundle
产物时,比如脚本工具,则需要在脚本启动的同时加载资源bundle
。
访问资源文件
在编译有资源文件的Package
时,会自动创建并添加到 module
中一个文件: resource_bundle_accessor.swift
,内容如下:
import Foundation
extension Bundle {
static let module = Bundle(path: "\(Bundle.main.bundlePath)/path/to/this/targets/resource/bundle")
}
对于 Swift
和 OC
调用:
//Swift
let path = Bundle.module.path(forResource: "image", ofType: "png")
//OC
NSString *path = [SWIFT_MODULE_BUNDLE pathForResource:@"image", ofType:@"png"];
由于
module
是内部属性,这种方式只能访问自己模块内部的资源文件,无法跨模块访问。如果想在一个公共模块提供外部模块使用的资源,则需要自己创建一个资源访问器。类似Cocoapods
的resource_bundle
的功能,可以采用bundle
路径方式访问。
本地化
LanguageandLocaleIDs
1.配置默认语言
name: "SwiftPackageByXcode",
//默认语言,需要配置在 platforms 前面
defaultLocalization:"en",
//平台配置
platforms: [
.iOS(.v10), //枚举定义在 IOSVersion 中
],
2.工程文件
根据需要的语言创建对应的文件夹,文件名为对应的语言,后缀命名成.lproj
,并在文件夹中创建 .strings
或者 .stringsdict
文件:
3.使用
Button(
action:testAction,
label: {
Text("TestKey",
bundle: Bundle.module
)
.font(.title)})
二进制依赖
Swift Package
在集成二进制文件时不需要任何特殊的设置,它们也是一个普通的Package.product
,在 target
的 dependencies
里通过名字指定即可。
⚠️:Swift 5.3
之后可用。目前仅支持xcframework
格式(也就是说.a
,.dylib
,.framework
需要包装成xcframework
)。更多讨论
package集成二进制文件
Swift 5.3
新增了一种新的Target
类型binaryTarget
来指定打包好的二进制文件。
在分发二进制依赖时注意点:
- 目前只支持苹果平台,为了实现的便捷复用了已有的
XCFramework
格式,它支持动态和静态链接,并且可以同时支持多个平台。 -
SPM
支持zip
和原始xcframework
文件以实现二进制依赖性。如果使用zip
要确保xcframework
位于根目录。 - 支持本地路径或者
https
链接。 - 在使用本地路径时指向的可以是
XCFramework
的路径或者是XCFramework
压缩后的zip
文件,而https
链接则只能指向zip
文件。 -
Swift
不会对二进制依赖性进行验证,意味着需要自己确保提供正确且有效的文件。
URL方式
.binaryTarget(
name: "HotpotCat",
url: "https://github.com/binxiao0604/HotpotCatTest/releases/download/1.0.0/HotpotCat.xcframework.zip",
//1.下载下来HotpotCat.xcframework.zip
//2.需要在本工程计算,swift package compute-checksum /Users/zaizai/Desktop/HotpotCat/HotpotCat.xcframework.zip
checksum: "f529b4ec593dbad862c3d4e5775506caedeed6f6b3a0d9199eb31a4db11c5318"
)
可以通过执行swift package compute-checksum <path-to-zip>
生成校验和。 需要确保首先下载二进制文件以计算校验和。
1.先将
zip
下载到本地。2.在
Swift Package
工程计算。问题:
1.artifact of binary target 'HotpotCat' has changed checksum; this is a potential security risk so the new artifact won't be downloaded
这里更改版本也就是URL和checksum需要清理缓存重新编译
原始文件方式(RAW FILE)
可以通过将二进制文件放入源中来添加二进制文件
目录结构:
.binaryTarget(
name: "Cat",
path: "framework/Cat.xcframework"
),
编译后的产物:URL
引入和本地引入的XCFramework
最终会以.framework
存在。
那么下载的远程
XCFramework
在哪呢?SourcePackages->artifacts->PackageName->XXX. xcframework
Package
中调用Cat
和HotpotCat
:
//HopotPackage.swift
import Cat
import HotpotCat
open class HotpotPackageTest {
public init(){}
open func test() {
let cat = Cat()
cat.cat()
HPTest().hpTest()
}
}
完整配置:
let package = Package(
name: "HopotPackage",
platforms: [
.macOS("10.15"),
.iOS(.v13)
],
products: [
.library(
name: "HopotPackage",
targets: ["HopotPackage"]),
],
dependencies: [
],
targets: [
.target(
name: "HopotPackage",
//如果要在本项目使用,需要依赖Target
dependencies: ["Cat","HotpotCat"]
),
.binaryTarget(
name: "Cat",
path: "framework/Cat.xcframework"
),
.binaryTarget(
name: "HotpotCat",
url: "https://github.com/binxiao0604/HotpotCatTest/releases/download/1.1.0/HotpotCat.xcframework.zip",
//1.下载下来HotpotCat.xcframework.zip
//2.需要在本工程计算,swift package compute-checksum /Users/zaizai/Desktop/HotpotCat/HotpotCat.xcframework.zip
//3.这里更改版本也就是URL和checksum需要清理缓存重新编译
checksum: "7d8bdb4297ff837ffe2ae87d211283c8fa5b5ab9199540b1617da1a63ce638a2"
)
]
)
其它Package集成包含二进制文件的Package
包含了二进制文件的 Package
在集成时不需要任何特殊的设置,它们也是一个普通的 Package.product
,和普通Package
一样:
- 工程的
Package dependencies
添加依赖 -
target
的dependencies
里通过名字指定
dependencies: [
// .package(url: "https://github.com/binxiao0604/HotpotPackage.git", .branch("master"))
.package(name:"HopotPackage",url: "https://gitee.com/guaizaizaiguai/HotpotPackage.git", .branch("master"))
],
targets: [
.target(
name: "HotpotPackageTest",
dependencies: ["HopotPackage"]),
.testTarget(
name: "HotpotPackageTestTests",
dependencies: ["HotpotPackageTest"]),
]
调用:
// HopotPackageTest.swift
open class HotpotPackageTest {
public init() {}
open func test() {
print("HotpotPackageTest test function");
HotpotPackage().test()
}
}
//HotpotPackageTestTests.swift
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
HotpotPackageTest().test()
}
输出:
HotpotPackageTest test function
XCFramework Cat Test
Test XC Framework
主工程引用
直接添加Package
然后调用:
HotpotPackage().test()
输出:
XCFramework Cat Test
Test XC Framework
对于非xcframework
形式的二进制,引入直接报错。必须为xcframework
格式
对于非
xcframework
二进制:.a
,.dylib
->.framework
-> . xcframework
其它
1.通过预处理命令区分编译环境
Package
可以通过 SPM
执行 swift build
进行编译,也可以通过生成 xcodeproj
通过 Xcode
进行编译,两者的编译环境并不相同,生成的可执行文件也不是同一个地址,所以可以通过 SWIFT_PACKAGE
区分编译环境:
#if SWIFT_PACKAGE
import Foundation
#endif
2.Xcode运行package
swift build
默认不会生成 packageName.xcodeproj
这种 Xcode
可以直接打开的工程文件。可以通过swift package generate-xcodeproj
命令行生成一个 .xcodeproj
文件, 然后就可以通过 Xcode
运行项目了。
SPM 与 Cocoapods 和 Carthage 对比
对比项 | SPM | Cocoapods | Carthage |
---|---|---|---|
原理 |
Swift 构建系统集成在一起,可以自动执行依赖项的下载,编译和链接过程 |
Cocoapods 会将所有的依赖库都放到另一个名为Pods 的项目中,然后让主项目依赖Pods 项目 |
自动将第三方框架编程为Dynamic framework (动态库) |
语言 |
Swift 、C 、CXX 、OC
|
Swift 、C 、CXX 、OC
|
OC 、Swift
|
兼容性 | 兼容 CocoaPods ,Carthage
|
兼容Carthage ,SPM
|
兼容 CocoaPods ,SPM
|
库支持力度 | 大部分支持,但少于CocoaPods
|
多,基本大部分都支持 | 大部分支持,但少于CocoaPods
|
复杂度 | 低 | 中 | 高 |
管理中心 | 没有统一管理的中心,没有更新中心服务器的文件索引这种耗时步骤 | 有统一管理的中心,有更新中心服务器的文件索引这种耗时步骤 | 没有统一管理的中心,没有更新中心服务器的文件索引这种耗时步骤 |
自动化 | 需要有一定目录格式 | 提供各个源管理仓库配置文件,更新仓库文件索引可能会很慢。 |
Carthage 只会帮你把各个库下载到本地,具体的 Project 配置需要自己处理 |
侵入性 | 无 | 高 | 无 |
编译速度 | 慢 | 慢 | 快 |
源码 | 可见 | 可见 | 不可见 |
生态 | 不够成熟,还有很多待优化项,官方开发,Xcode 自集成 | 成熟 | 差 |
缓存 | 项目根目录的缓存,不同项目需要重新下载 | 除了项目根目录的缓存,还有本地缓存体系,不同工程会直接从本地copy。 | 项目根目录的缓存,不同项目需要重新下载 |
错误问题
1.contains mixed language source files; feature not supported
https://www.reddit.com/r/SwiftPM/comments/iay38g/build_c_and_swift_package/
1个Target
只能支持swift
或者oc
不能混编。
2.Argument 'dependencies' must precede argument 'publicHeadersPath'
dependencies
必须放在publicHeadersPath
前面。
3.An unknown error occurred. SecureTransport error: connection closed via error (-1)
一般都是网络问题,建议开代理或者切换源。
4.An unknown server error occurred. Make sure a connection is established and the server is accessible.
一般都是网络问题,建议开代理或者切换源。
5.public headers directory path for 'SPMLibraryForOC' is invalid or not contained in the target
publicHeadersPath
路径配置有问题
6.Failed to parse the manifest file
一般遇见这个错误代表Package.swift
解析失败。
demo:
SPMTest
SwiftPackageByXcode
SPMLibraryForOCGitee版本(如果github连接有问题建议复制库到)码云。
参考:
SPM官网
Swift 论坛
Creating Swift Packages
WWDC 2018 SECTION 411
官方pdf文档
Swift packages: Resources and localization
Swift CI 持续集成
SPM GitHub 社区贡献
Trunk Snapshots / 快照
Swift Bug 跟踪
Swift 版本演化
Package.swift官方示例
https://developer.apple.com/forums/tags/wwdc20-10169
Creating a personal access token
github.com/apple/swift-evolution
https://blog.csdn.net/xinshou_caizhu/article/details/103325571
https://www.jianshu.com/p/ce49d8f32f77
https://juejin.cn/post/6844903619553132552
https://blog.csdn.net/sinat_35969632/article/details/108251008
https://blog.csdn.net/olsQ93038o99S/article/details/108612725
https://blog.csdn.net/weixin_39968592/article/details/111201874