Sprinter 0.2.1

Sprinter 0.2.1

测试已测试
语言语言 SwiftSwift
许可 MIT
发布最后发布2018年2月
SPM支持 SPM

Nick Lockwood 维护。



Sprinter 0.2.1

  • Nick Lockwood

Travis
Coveralls
Swift 3.2
Swift 4.0
License
Twitter

Sprinter

简介

是什么?

Sprinter 是一个用于 Mac 和 iOS 的库,它通过使用 printf/NSLog 格式标记约定在运行时格式化字符串。

目标是提供一个与 printf 规范完全兼容并为 Swift 提供友好接口的类型安全字符串格式化接口,同时兼容 Apple 的用于处理 Objective-C 数据类型的专有扩展。

"Sprinter" 这个名字来源于 "String-Printer",就像是 C 标准库中的 sprintf 函数。

为什么?

虽然 Swift 已经以 String(format:arguments:) 初始化器形式提供了字符串格式化支持,但 Swift 的支持只是对 Objective-C API 的一种比较粗糙的封装,并且缺少一些标准的 printf 格式化功能和数据类型支持。例如,在 Swift 中无法使用以下格式字符串:

"Hello %s, how are you?"

因为 %s 标记期望一个 C 字符串(一个指向以空字符结束的 CChar 数组的指针),而 Swift 的 String(format:arguments:) 方法不会接受它。相反,您必须使用平台特定的 %@ 标记,这限制了字符串在不同平台之间的重用性。

Swift 也无法提供验证或检查格式字符串的方法。如果格式包含错误,或者在代码中的格式参数与代码中的参数不匹配,则字符串将在运行时显示错误,或者更糟,可能会导致崩溃或静默内存损坏。

Sprinter 通过公开每个格式字符串的参数类型来解决这些问题,因此您可以编写运行时验证逻辑并优雅地处理错误。

Sprinter 库也可以用作基于字符串构建时验证的单元测试的基础,甚至是代码生成管道的一部分,以提供强类型字符串属性和方法。

如何?

Sprinter 基于原始的 IEEE printf spec 以及 Apple 的Objective-C扩展 实现了一个健壮的字符串格式解析器。它利用 Swift 的字符串格式器进行内部处理,但执行预验证和参数类型转换,以确保永远不会传递无效类型到底层实现。

Sprinter 包含一个综合的测试套件以确保符合规范,并保证与 Apple 格式器的输出兼容。

用法

安装

Sprinter API 整个封装在一个文件中,所有公开的都带有前缀或命名空间,因此您只需将 Sprinter.swift 文件拖入您的项目,即可使用它。如果愿意,Mac 和 iOS 上有一个框架可供导入,或者您可以在 Linux 上使用 CocoaPods、Carthage 或 Swift Package Manager。

要使用 CocoaPods 安装 Sprinter,请在 Podfile 中添加以下内容

pod 'Sprinter', '~> 0.2.0'

Sprinter 兼容 Swift 3.2 和 4.x,支持 iOS 9 或 macOS 10.0 及以上版本

集成

要使用 Sprinter 格式化字符串,您首先创建一个 FormatString 实例,如下所示

let formatString = try FormatString("I have %i apples and %i bananas")

注意 try 关键字 - FormatString 构造函数会验证字符串,如果格式无效,则会抛出错误。构造格式字符串对象后,您可以使用 print() 方法输出格式化字符串。该方法支持可变参数,方便传递参数。还有一种形式接受一个参数数组。

您可以使用 print() 方法如下所示

let string = try formatString.print(5, 6)
print(string) // I have 5 apples and 6 bananas

您会注意到 print() 函数也要求 try。如果传入的参数与原始格式字符串的占位符不匹配,则该方法会抛出错误。由 FormatString 构造函数或 print() 方法抛出的错误都将为 FormatString.Error 类型,例如

let formatString = try FormatString("I have %y apples") // throws FormatString.error.unexpectedToken("y")

let string = try FormatString("I have %i apples").print("foo") // throws FormatString.error.argumentMismatch(1, String.self, Int.self)

在调用 print() 方法之前,您可以通过使用 FormatStringtypes 属性来确定所需的参数类型,该属性返回一个 Swift 类型值数组

let types = formatString.types
print(types) // Int, Int

这通常在运行时没有太大用处(错误的参数将是一个编程错误,应该在发布之前得到修复),但它可以用于自动化测试,以验证给定本地化字符串键在不同语言中具有相同的参数类型。

本地化

FormatString 构造函数还接收一个可选的 locale 参数,可用于本地化输出

let french = try FormatString("I have %i apples", locale: Locale(identifier: "fr-FR"))

这将影响特定于区域设置的对齐和标点符号的显示方式,例如

let english = try FormatString("%'g", locale: Locale(identifier: "en-US")
try print(english.print(1234.56)) // 1,234.56

let french = try FormatString("%'g", locale: Locale(identifier: "fr-FR")
try print(french.print(1234.56)) // 1 234,56

let german = try FormatString("%'g", locale: Locale(identifier: "de-DE")
try print(german.print(1234.56)) // 1.234,56

线程安全

在后台线程上创建 FormatString 实例是安全的。

创建后,给定的 FormatString 实例是无状态的,因此相同的实例可以安全地在多个线程上并发打印字符串。

高级用法

在打印之前创建 StringFormat 对象可能看起来有些繁琐,但它有两个作用

  1. 它允许在使用之前验证和检查字符串的类型。这意味着您可以在调用时确信不会出现意外的错误。

  2. 昂贵的字符串解析和 NumberFormatter 初始化步骤只需执行一次,然后存储起来,无需每次字符串显示时都重复执行。

因此,建议您存储并重用您的 FormatString 对象。您可以在一开始做这件事,也可以在串行第一时间每个字符串显示时做这件事 - 无论哪一个对您的应用更有意义。

一种好的方法是创建一个封装器函数,封装您的应用特定的字符串要求。例如,您可能希望在产品中忽略字符串格式错误(因为那时修复得太晚),只显示一个空字符串。以下是在您的应用中可能使用的示例封装器

private var cache = [String: FormatString]()
private let queue = DispatchQueue(label: "com.Sprinter")

func localizedString(_ key: String, _ args: Any...) -> String {
    do {
        var formatString: FormatString?
        queue.sync { formatString = cache[key] }
        if formatString == nil {
            formatString = try FormatString(NSLocalizedString(key, comment: ""), locale: Locale.current)
            queue.async { cache[key] = formatString }
        }
        return try formatString?.print(arguments: args) ?? ""
    } catch {
        // Crash in development, but not in production
        assertionFailure("\(error)")
        return ""
    }
}

此函数提供

  • 一个方便的 API 用于显示 Localizable.strings 文件中的键
  • 封装的错误处理,开发中将崩溃,但在产品中将优雅地失败
  • 线程安全的 FormatString 实例缓存,以获得更好的性能

这只是示例方法,但对于大多数用例都应有效。