Swift 开发 Linux 和 macOS 应用的利器 —— PerfectLib 食用全指南

发布于:2017-02-11 22:48,阅读数:3566,点赞数:14


>[「Perfect」](https://www.perfect.org)是一款 Swift 服务端框架,而[「PerfectLib」](https://github.com/PerfectlySoft/Perfect)是其重要组件之一

# 引言

`PerfectLib`是`Perfect`的重要组件。一个服务端软件离不开和文件、系统打交道,正是`PerfectLib`在背后默默支撑着。而这个组件是一个独立的 Module,是可以单独使用的。这就意味着即使开发本地软件也是可以拿来用的。

那么今天就不讲服务端,只讲这个组件。

# 功能

在[「官方文档基本工具页」](https://www.perfect.org/docs/utilities_zh_CN.html)中,一部分内容是留给`PerfectLib`的,中文文档这样描述它:

>一系列用于搭建服务器和客户机应用程序所需要的一系列工具类基本函数库。

因此在开发客户机程序时候也可以使用这些工具,能大大提高开发效率。最重要的是,在 macOS 下通用。由于大多数基于C语言接口开发,在目前情况看,兼容性比 Swift 标准库好。

## 组分

截止笔者写文时,这一栏包含:

- ✔️**字节流转换**
- ✔️**文件操作**
- ✔️**目录与路径**
- ❌线程 —— [「Perfect-Thread」](https://github.com/PerfectlySoft/Perfect-Thread)
- ❌UUID唯一标识符 —— Foundation
- ✔️**SysProcess系统进程**
- ✔️**日志**
- ❌CURL联网传输 —— [「Perfect-CURL」](https://github.com/PerfectlySoft/Perfect-CURL)
- ❌Zip压缩 —— [「Perfect-Zip」](https://github.com/PerfectlySoft/Perfect-Zip)

下面就来分别看看这些工具类是如何使用的。

## Bytes —— 字节流

其结构包含一个`UInt8`数组用于存放数据和一个`position`属性。

```swift
/// The position from which new export operations begin.
public var position: Int
/// The underlying UInt8 array
public var data: [UInt8]
```

其包含一组`import`和一组`export`方法来处理字节流。

```swift
public func import8Bits(from frm: UInt8) -> PerfectLib.Bytes
public func import16Bits(from frm: UInt16) -> PerfectLib.Bytes
public func import32Bits(from frm: UInt32) -> PerfectLib.Bytes
public func import64Bits(from frm: UInt64) -> PerfectLib.Bytes
public func importBytes(from frm: [UInt8]) -> PerfectLib.Bytes .Bytes
......

public func export8Bits() -> UInt8
public func export16Bits() -> UInt16
public func export32Bits() -> UInt32
public func export64Bits() -> UInt64
public func exportBytes(count cnt: Int) -> [UInt8]
......
```

它是以字节队列形式处理。当`import`字节时,加入的数据会被`append`入`data`结尾,而当输出时会从数组开头进行输出并且移动`position`位置。输出的数据并不会被删除,仅仅是移动了`position`使下次输出时能从上次的位置继续输出。因此此工具不适合处理大量数据以及拥有过长的生命周期,仅限于临时使用。

基于它直接操作字节的特性,可以用它来拼接整形:

```swift
let bytes = Bytes.init()
bytes.importBytes(from: [0x01, 0xed])
print(bytes.export16Bits())
//输出:60673
```

需要注意的是数字大小端的问题,可以从这个例子看出大小端:

```swift
bytes.importBytes(from: [0x01, 0xed, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
print(bytes.export64Bits())
//输出:60673
```
```swift
let bytes = Bytes.init()
bytes.importBytes(from: [0x01, 0xed, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])
print(bytes.export64Bits())
//输出:72057594037988609
```

上面例子中仅有最后一位数据不同,很容易就能看出大小端了。

## Dir —— 目录与路径

利用`Dir`可以对目录进行基本的操作。但事实上`Dir`并不是一个目录的抽象,而是一个路径的抽象。构造一个`Dir`对象并不意味着一定要有这个目录,相反的这个目录可以是不存在的。因此用路径来称呼更加合适一点。

下面笔者将分组进行分析帮助读者理解:

### 基本

这组属性包含了路径常规的属性:

```swift
public var exists: Bool { get }
public var name: String { get }
public var path: String { get }
public var parentDir: PerfectLib.Dir? { get }
```

### 权限

权限类型被定义为了`PermissionMode`,实际为C语言桥接的`mode_t`类型。而这个类型在 masOS 叫`__darwin_mode_t`,实际数据为一个16位无符号整形。

`perms`属性记录了目录的权限:

```swift
public typealias PermissionMode = PerfectLib.File.PermissionMode
public var perms: PerfectLib.Dir.PermissionMode
```

### 操作和遍历

创建、删除以及遍历方法。这三个方法都是基于C语言接口的,macOS 中接口存在于`Darwin`中,如果失败都会抛出异常。当C语言接口返回异常值时框架会调用`strerror`方法获取错误信息并封装成`PerfectError.fileError`异常抛出。

```swift
public func create(perms: PerfectLib.Dir.PermissionMode = default) throws
public func delete() throws
public func forEachEntry(closure: (String) throws -> ()) throws
```

### 辅助

一个用来存储工作目录的静态属性,方便开发者使用。

```swift
public static var workingDir: PerfectLib.Dir { get }
public func setAsWorkingDir() throws
```

## File —— 文件操作

这是一个很全的文件操作库。不仅包含了针对文件的操作,还包含了对文件流的操作。它可以以数据流的形式对文件进行改写、追加等操作,这跟笔者的一个工具库功能十分相似:[EMFileStream —— 基于 stdio 的 Swift 文件流操作库](http://posts.enumsblog.com/posts/16011)

下面读者也将对属性和方法进行分类分析:

### 基本

一组从属性名就能看出是什么的基本属性。比较眼生的大概也就只有fd,文件描述,笔者也喜欢称它为句柄,打开一个文件时的一个`token`。

```swift
public var fd: Int
public var exists: Bool { get }
public var isDir: Bool { get }
public var path: String { get }
public var modificationTime: Int { get }
public var size: Int { get }
public var perms: PerfectLib.File.PermissionMode
```

### 符号链接属性

如果该文件是个符号链接`symbolic link`,那么这两个属性能帮助开发者找到它指向的目标。

```swift
public var isLink: Bool { get }
public var realPath: String { get }
```

### 文件操作

同样是一组从名字就能看出的功能,眼生的大概有:

- marker: 代表目前文件流读取位置距离文件头位置的偏移量,底层实现使用了一个特别特别的方式。对于C语言文件操作比较熟悉的读者应该知道获取偏移量通常会使用`ftell`这个函数,而这里的实现使用了`lseek`函数。`lseek`是一个移动动作,通过参数选项能使文件指针从文件头、尾、当前位置其中之一为基准进行偏移,同时返回离文件头的距离。框架作者使用`lseek`从当前位置移动了0个字节来获取偏移量,这个做法有点诡异,大概是出于I/O性能考虑?

- abandon: 重置文件状态,如果文件是打开状态就关闭它。

```swift
public var marker: Int
public var isOpen: Bool { get }
public func open(_ mode: PerfectLib.File.OpenMode = default, permissions: PerfectLib.File.PermissionMode = default) throws
public func close()
public func delete()
public func abandon()
public func moveTo(path: String, overWrite: Bool = default) throws -> PerfectLib.File
public func copyTo(path pth: String, overWrite: Bool = default) throws -> PerfectLib.File
```

### 流读写

这几个方法能以流的形式从当前位置进行文件读写。但笔者使用的版本中并没有看到`seek`相关的方法,实现是非常容易的但是如果没有`seek`就失去了随机读写的功能。

```swift
public func readSomeBytes(count: Int) throws -> [UInt8]
public func readString() throws -> String
public func write(string: String) throws -> Int
public func write(bytes: [UInt8], dataPosition: Int = default, length: Int = default) throws -> Int
```

### 文件锁定

使用C语言方法`lockf`锁定文件。需要注意的是这个锁只会影响当前进程,对其他进程是无效的。跟`NSLock`类似的,`lock`方法也是阻塞式的。使用中的文件在解锁之前,新的锁定方法会被阻塞,当文件关闭时锁定会被自动解除。

```swift
public func lock(byteCount: Int) throws
public func unlock(byteCount: Int) throws
public func tryLock(byteCount: Int) throws
public func testLock(byteCount: Int) throws -> Bool
```

## SysProcess —— 系统进程

这是一个非常`Powerful`的工具,它可以调用其他程序,甚至运行脚本。在服务端开发中这可以说是必不可少的一个工具。

它纯粹基于C语言实现,而在这之前,需要实现这个功能需要使用以下这个拥有三个名字的类实现,并通过管道获得输入输出。以下这三个类都是同一个东西,接口都是一样的,只是适用场合不同:

- `NSTask`: 原 Objective-C 类
- `Process`: 在 macOS 中的 Swift 类
- `Task`: 在 Linux 中的 Swift 类

以下笔者也将分组进行分析。

### 基本

以命令、参数和环境参数的形式构造进程。进程一旦被构造就立即开始运行了,此时 Swift 程序是不会被阻塞的,程序还将继续运行。`pid`属性包含了新进程的`pid`。

```swift
public var pid: pid_t
public init(_ cmd: String, args: [String]?, env: [(String, String)]?) throws
public func isOpen() -> Bool
```

### 标准输入、输出和错误

标准输入、输出和错误以文件的形式被定义,利用上面提到的文件流操作在这里可以进行交互。需要注意的是当没有输出时文件读取方法会被阻塞,而当进程结束时读取方法会抛出错误跳出。但当前版本在 Linux 下存在问题,在进程结束时阻塞的文件读取方法并没有按预期抛出异常,导致程序永久阻塞。

```swift
public var stdin: PerfectLib.File?
public var stdout: PerfectLib.File?
public var stderr: PerfectLib.File?
```

### 进程操作

当进程被构造时会立即开始运行,如需要等待进程返回需要使用`wait`方法等待进程返回。而`kill`和`close`方法会杀死进程。`detach`方法负责进程脱离。

```swift
public func wait(hang: Bool = default) throws -> Int32
public func kill(signal: Int32 = default) throws -> Int32
public func close()
public func detach()
```

## 日志 —— Log

框架所带的日志包含两个内容:

- ConsoleLogger: 控制台输出
- SysLogger: 调用C接口`vsyslog`写入日志

这两部分遵守了`Logger`协议,包含:

```swift
func debug(message: String)
func info(message: String)
func warning(message: String)
func error(message: String)
func critical(message: String)
func terminal(message: String)
```

以不同的`TAG`输出不同的内容

## 其他

除此之外,`PerfectLib`还包含了以下组件:

- `JSONConvertible`: 不怎么好用的 JSON 解析工具,不推荐。
- `Utilities`: 一些系统类的扩展,如想知道细节请直接读源码。

# 结语

其实`Perfect`虽然是一款服务端框架,但用于支撑服务端的组件并不是都跟服务端开发有关。这些基本的工具也是能在日常客户端开发中使用的。另外笔者也开过脑洞,尝试过在`iOS`上使用。但`SysProcess`所依赖的接口在`iOS`下出了点问题,也没有深入研究去解决。

在 Linux 上开发 Swift 应用也是个比较新鲜的话题。有越来越多的优秀开源库出现,相信情况只会越来越好。拭目以待吧。


评论:0条


返回列表

返回归档

返回主页