Swift 5.1现在已经正式发布,尽管只是次要版本,它包含了大量的更改和改进。从基本的新功能,例如模块稳定性(使SDK供应商可以交付预编译的Swift框架)到所有SwiftUI以及其他功能的新语法功能。
除了具有标题的新功能外,Swift 5.1还包含许多较小的但仍然非常重要的新功能和改进。乍一看,这种变化似乎很小,甚至是不必要的,但可能会对我们编写和构建Swift代码的方式产生重大影响。
Swift 5.1 - 简书
return
关键字,这使得在声明更简单便捷的API时非常友好://单行表达式
func increase(number: Int) -> Int {
number + 1
}
//计算属性
struct Message {
var title: String
var info: String
let description: {title + ": " + info}
}
struct Message {
var title: String
var info: "body"
}
在swift 5.1 中,下方初始化方法均正确
var message = Message(title: "title")
var message = Message(title: "title", info: "info body")
class ListViewController: UITableViewController {
static let cellReuseIdentifier = "list-cell-identifier"
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(
ListTableViewCell.self,
forCellReuseIdentifier: Self.cellReuseIdentifier
)
}
}
Swift
的Self
关键字(或类型)使我们能够在未知具体类型的上下文中动态引用实际上的类型,例如,通过在协议扩展中引用协议的实现类型:extension Numeric {
func incremented(by value: Self = 1) -> Self {
return self + value
}
}
Numeric
协议扩展了一个自增的方法,但是我们现在不知道具体自增的类型,使用Self
作为返回类型,则可以动态获取对应的类型:let num1 = 5.incremented() //num1: Int
let num2 = 5.0.incremented() //
Self
的范围现已扩展到还包括具体类型(例如枚举,结构体和类),使我们能够将Self用作一种引用方法或属性的封闭类型的别名,如下所示:struct TextTransform {
let closure: (String) -> String
}
extension TextTransform {
static var capitalize: Self {
return TextTransform { $0.capitalized }
}
static var removeLetters: Self {
return TextTransform { $0.filter { !$0.isLetter } }
}
}
我们现在可以在上方使用Self
而不是完整的TextTransform
类型名称看,当然这纯粹是语法糖——但它可以使我们的代码更紧凑,尤其是在处理长类型名称时。我们甚至还可以在方法或属性中使用Self内联,同时使用隐式返回,进一步使上述代码更加紧凑:
extension TextTransform {
static var capitalize: Self {
Self { $0.capitalized }
}
static var removeLetters: Self {
Self { $0.filter { !$0.isLetter } }
}
}
给String扩展两个方法:
extension String {
func withTransform(_ textTransform: TextTransform) -> String {
textTransform.closure(self)
}
mutating func withTransforms(_ textTransforms: [TextTransform]) -> String {
textTransforms.forEach{ trans in
self = self.withTransform(trans)
}
return self
}
}
let singelUse = "i am a string"
.withTransform(.capitalize)
.withTransform(.removeLetters)
var str = "i am a string"
let groupUse = str.withTransforms([
.capitalize,
.removeLetters
])
在 iOS 开发中,经常要用到@IBOutlet
、@IBAction
,在Swift
中,越来越多@
修饰的关键字出现,比如 @UIApplicationMain
,特别是在 SwiftUI 中,会发现有很多类似这样的关键字。
@propertyWrapper
@propertyWrapper
struct Trimmed {
private var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
}
init(wrappedValue initialValue: String) {
self.wrappedValue = initialValue
}
}
struct Message {
@Trimmed var title: String
@Trimmed var info: String
}
//任何字符串无论是在初始化期间还是通过后面的属性访问都会自动删除前后面的空格。
var msg = Message(title: " Swift5.1 Property Wrappers ", info: " is a new and important key words")
let title = msg.title // "Swift5.1 Property Wrappers"
let info = msg.info // "is a new and important key words"
ordered collection diffing
)。 随着Combine
和SwiftUI
之类的工具越来越接近声明式编程的世界,能够计算两种状态之间的差异变得越来越重要。DatabaseController
,它将使我们可以使用一系列内存模型轻松地更新磁盘上的数据库。为了能够确定是应该插入还是删除模型,我们现在可以简单地调用新的差异API来计算旧数组与新数组之间的差异-然后迭代该差异中的更改以执行我们的数据库操作:class DatabaseController<Model: Hashable & Identifiable> {
private let database: Database
private(set) var models: [Model] = []
...
func update(with newModels: [Model]) {
let diff = newModels.difference(from: models)
for change in diff {
switch change {
case .insert(_, let model, _):
database.insert(model)
case .remove(_, let model, _):
database.delete(model)
}
}
models = newModels
}
}
inferringMoves
方法,然后查看每个插入是否与移除关联,如果这样,则将其视为移动,如下所示:func update(with newModels: [Model]) {
let diff = newModels.difference(from: models).inferringMoves()
for change in diff {
switch change {
case .insert(let index, let model, let association):
// 如果 associated index 不为 nil, 则意味着这个inset操作实际上是move
if association != nil {
database.move(model, toIndex: index)
} else {
database.insert(model)
}
case .remove(_, let model, let association):
// 我们将只处理 association 为 nil的情况,其他情况已经在上方处理
if association == nil {
database.delete(model)
}
}
}
models = newModels
}
protocol TokenParser {
func parseToken(from string: String) throws -> Token
}
struct KeywordParser: TokenParser {
func parseToken(from string: String) throws -> Token {
...
}
}
struct TextParser: TokenParser {
//这仍然能够符合我们的协议,虽然它没有 throws
func parseToken(from string: String) -> Token {
...
}
}
let parsers: [TokenParser] = ...
for parser in parsers {
let token = try parser.parseToken(from: string)
}
let parser = TextParser()
let text = parser.parseToken(from: string)
ExpressibleByStringInterpolation
——使类型可以使用字符串插值struct Path {
var string: String
}
func loadFile(at path: Path) throws -> File {
...
}
Path
类型确实给我们带来了很多好处,但它也可能使我们的API使用起来更加麻烦。例如,任何时候我们想要使用字符串文字来指定路径时,我们现在都必须先将其包装起来:try loadFile(at: Path(string: "~/documents/article.md"))
ExpressibleByStringLiteral
,这使我们能够直接将字符串文字传递给任何接受Path的API:extension Path: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self.string = value
}
}
try loadFile(at: "~/documents/article.md")
try loadFile(at: "/users/\(username)/file.txt")
现在,swift5.1引入了新协议ExpressibleByStringInterpolation
,只要使Path
遵循这个协议,则上面的代码就可以正常运行了,增加如下代码:
extension Path: ExpressibleByStringInterpolation {}
some
关键字这里的some其实就是和一个称为opaque(不透明)类型有关,在返回类型前面加上一些关键字表示返回类型是不透明的,不透明类型通常被称为反向泛型类型。
比如我定一个一个Animal
协议,具有关联类型LikeType
protocol Animal {
associatedtype LikeType
var name: String { get }
var like: LikeType { get }
}
struct Dog: Animal {
var name: String
var like: Bark
}
struct Pig: Animal {
var name: String
var like: Sleep
}
此时我们实现一个方法去识别动物:
func identityAnimal() -> Animal
这在swift中是无法编译通过的,因为swift不能把带有关联类型的协议类型作为返回类型,这个时候就轮到some
上场了:
func identityAnimal() -> some Animal {
return Pig(name: "pink pig", like: Sleep("every day"))
}
var animal: some Animal = identityAnimal()
参考文档: 5 small but significant improvements in Swift 5.1 Implementing throwing protocol functions as non-throwing Making types expressible by string interpolation