Swift Package Manager制作和使用

2,702 阅读3分钟

什么是Swift Package Manager

Swift Package Manager(简称SPM)是苹果在2018年推出的供Swift开发者进行包管理的工具。类似于我们常使用的Cocoapods或Carthage。

那为什么苹果要推出SPM呢?它又有哪些方面的优势呢?

SPM的优势

1.导入第三方库非常简单。有可视化导入界面,按流程简单操作几步就可以完成导入。

2.定义文件简单。只需将源码放入对应的文件夹内即可,Xcode就会自动生成工程文件,并生成编译目标产物所需要的相关配置。

3.SPM是通过library或workspace方式集成的,侵入性非常低。

4.SPM多平台编译的能力非常好。UIKit一次编写即可适配iOS/iPadOS/tvOS/watchOS多个平台。

5.SPM与Cocapods兼容,可以一起使用。

下面介绍了SPM的优势,简单说下SPM、Cocoapods和Carthage的差异。

SPM、Cocoapods和Carthage差异

注:

 SPM在入侵性和编译速度上有明显的优势。

根据上面说的SPM的优势,下面先尝试下制作一个简单的Package.

制作Swift Package

下面以一个简单的打印为例。

使用 SPM 搭建开发框架

$ mkdir CommandLineTool
$ cd CommandLineTool
$ swift package init --type executable

为了开发 command line tool(CLI),我们需要创建一个新的文件夹,并使用 swift package manager(SPM)来初始化项目。
最后一行的 type executable 参数将告诉 SPM,我们想创建一个 CLI,而不是一个 Framework。
创建完成后,会得到如下结构: image.png

  • Package.swift 文件:用于描述当前 Package 的信息及其依赖,需要记住的是,在 SPM 的世界里,不再有 Pod 的概念,与之对应概念是 Package,而 CLI 本身也是一个 Package
  • main.swift 文件:这个文件在 Sources 目录下,它代表整个命令行工具的入口,另外记住不要更换这个文件的名字!
  • Tests 文件夹:这个文件夹是用于放置测试代码的。
  • .gitignore 文件:通过这个文件,git 会自动忽略 SPM 生成的 build 文件夹(.build 目录)以及 Xcode Project

将代码划分为 framework 和 executable

将源代码分成两个模块,一个是 framework 模块,一个是 executable 模块。 这样做的原因有 2 点:

  • 会让测试变得更加容易
  • 让你的命令行工具也可以作为其他工具依赖的 Package

具体怎么做呢?

首先,我们要保证 Sources 目录下有两个文件夹,一个用于存放 executable 相关的逻辑,一个用于存放 framework 相关的逻辑,就像下面一样:

$ cd Sources 
$ mkdir CommandLineToolCore

SPM 的一个非常好的方面是,它使用文件系统作为它的处理依据,也就是说,只要采用上述操作提供的文件结构,就等于定义了两个模块。 紧接着,我们在 Package.swift 里定义了两个target, 一个是 CommandLineTool 模块,一个是 CommandLineToolCore.

// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "CommandLineTool",
    products: [
        .executable(name: "CommandLineTool", targets: ["CommandLineTool"])
    ],
    
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
        .package(url: "https://github.com/SwiftyJSON/SwiftyJSON.git", from: "5.0.0"),
        .package(url: "https://github.com/Alamofire/Alamofire.git",from: "5.6.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.
        .executableTarget(
            name: "CommandLineTool",
            dependencies: ["CommandLineToolCore"]),
        .target(
            name: "CommandLineToolCore",
            dependencies: ["SwiftyJSON"]),
        .testTarget(
            name: "CommandLineToolTests",
            dependencies: ["CommandLineTool"]),
    ]
)

通过上面这种方式,我们让 executable 模块依赖了 framework 模块。

为了能够在命令行和测试用例中方便的运行我们的代码,我们最好不要在 main.swift 中添加过多的逻辑,而是通过程序调用的方式唤起 framework 中的主逻辑。 为了实现这样的目的,我们需要创建一个名为 CommandLineTool.swift 的文件,将其放在 framework 模块中(Sources/CommandLineToolCore),它里面的内容如下:

import Foundation

public final class CommandLineTool {
    private let arguments: [String]

    public init(arguments: [String] = CommandLine.arguments) { 
        self.arguments = arguments
    }

    public func run() throws {
        print("Hello world")
    }
}

在 main.swift 中添加 run() 方法:

import CommandLineToolCore

let tool = CommandLineTool()

do {
    try tool.run()
} catch {
    print("Whoops! An error occurred: \(error)")
}
安装/更新依赖

一旦我们声明了新的依赖关系,只需要求 SPM 解析新的依赖关系并安装它们,然后重新生成 Xcode 项目即可。

$ swift package update
$ swift package generate-xcodeproj

在根目录下执行 swift build 吧,然后再执行 swift run 命令

$ swift build //构建swift package
$ swift run //执行swift package
> Hello world

swift package 可以用于获取package信息等,例如 swift package tools-version 可以获取到编译版本

如果接受入参,我们修改下CommandLineTool.swift文件:

import Foundation

public final class CommandLineTool {
    private let arguments: [String]

    public init(arguments: [String] = CommandLine.arguments) {
        self.arguments = arguments
    }

    public func run() throws {
        guard arguments.count > 1 else {
            print("arguments.count小于1")
            return
        }
        
        // The first argument is the execution path
        let fileName = arguments[1]
        print("Hello world--\(fileName)")
    }
}
编写单测

我们几乎已经准备好发布这个命令行工具了,但在这样做之前,我们还是需要通过编写一些测试来确保它真正的按照预期工作。 由于我们之前将整个项目划分成了 framework 和 executable 的结果,所以测试将变得十分容易。我们所要做的就是以程序调用的方式运行它,并断言它打印入参。
首先在 Package.swift 文件中添加一个测试模块,在你的 target 数组中添加以下内容。

targets: [
        .testTarget(
            name: "CommandLineToolTests",
            dependencies: ["CommandLineToolCore"]),
    ]

最后,重新生成 Xcode 项目:

$ swift package generate-xcodeproj

再次打开Xcode 项目,跳到 CommandLineToolTests.swift 中,添加以下内容:

import XCTest
import class Foundation.Bundle
import CommandLineToolCore

final class CommandLineToolTests: XCTestCase {
    func testExample() throws {
        // This is an example of a functional test case.
        // Use XCTAssert and related functions to verify your tests produce the correct
        // results.

        // Some of the APIs that we use below are available in macOS 10.13 and above.
        guard #available(macOS 10.13, *) else {
            return
        }

        // Mac Catalyst won't have `Process`, but it is supported for executables.
        #if !targetEnvironment(macCatalyst)
        
        let arguments = ["","你好,世界"]
        let tool = CommandLineTool(arguments: arguments)
        
        // Run the tool and assert that the file was created
        try tool.run()
        
        #endif
    }

    /// Returns path to the built products directory.
    var productsDirectory: URL {
      #if os(macOS)
        for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
            return bundle.bundleURL.deletingLastPathComponent()
        }
        fatalError("couldn't find the products directory")
      #else
        return Bundle.main.bundleURL
      #endif
    }
}

此外,还可以添加另一个测试,以验证在没有给定文件名或文件创建失败时是否抛出了正确的错误。 要运行测试,只需在命令行上运行 swift test 即可。

构建命令行工具

我们已经构建并测试了我们的命令行工具!下面开始,我们会尝试安装它,并使它能够在任何地方运行:

$ swift build -c release
$ cd .build/release
$ cp -f CommandLineTool /usr/local/bin/commandlinetool //将编译后的二进制文件移到 /usr/local/bin
$ commandlinetool hello.swift

SPM导入第三方库

下面以Alamofire为例,首先在github搜索Alamofire。

向下滑找到Swift Package Manager,复制URL

github.com/Alamofire/A…

打开Xcode

选择Add Package dependency...

粘贴URL搜索

输入GitHub连接地址:github.com/Alamofire/A…

选中Add Package

稍等一会儿!

选中Add Package 下一步

这一步已经添加完成了,需要使用的类中引入import Alamofire

总结

  • SPM是苹果推出的优于Cocoapods和Carthage的第三方库管理工具.

  • SPM以可视化,简单步骤为主。

  • SPM的核心在于Pacakage.swift文件,里面是以对象为基础的管理第三方库。

  • 掌握和运用SPM,让工作更加简单。

参考