Swift 5.0 语言新特性



原文地址

Raw String (原始字符串)

SE-0200
原始字符串中特定的字符不会被转义,例如 \ " 等。使用原始字符串时,只需要在字符串的开头和结尾添加 # 就可以了,例如 let rawString = #"This is a "raw" "String""#。由于开头和结尾的 # 是新的界定符号,所以 Swift 会把单独的 " 理解成普通的字符,而不是字符串的开始或结尾符号。
同样, 原始字符串中也可以使用 \ ,例如 let rawString = #"This is a "raw" "String" with keyPath \Person.name"# 。不过需要注意的是,由于 \ 的特殊性,在原始字符串中添加变量时,需要改为 \#(value)

1
2
let value = 42
let rawString = #"This is a raw string with \#(value)"#

有意思的是,如果字符串本身包含了 "# 这样的字符组合,原始字符串的开头和结尾就需要各添加一个 # ,来忽略字符串中的 "# ,例如下面的例子:

1
let str = ##"My dog said "woof"#gooddog"##

简单来说,也就是开头和结尾的 # 的个数必须相等。

原始字符串同样适用于多行字符串,只需要用 #""" 开头,以 """# 结尾。

1
2
3
4
5
let multi = #"""
The answer to life,
the universe,
and everything is \#(answer).
"""#

原始字符的加入,让正则表达式的声明变的更加直观。例如一个匹配 keypaths \Person.name 的正则表达式,在原始字符串出现之前的定义需要写出下面的代码。

1
let regex1 = "\\\\[A-Z]+[A-Za-z]+\\.[a-z]+"

各种对 \ 的转义,让表达式变的不直观。在原始字符串的加持之下,就变的简单明了了。

1
let regex2 = #"\\[A-Z]+[A-Za-z]+\.[a-z]+"#

Result 类型

SE-0235 提议介绍了 Result 类型,提供了简单明了的处理错误的方法。

在最近发布的 Xcode 10.2 beta1 中并没有带上这部分。实际 Result.swift 已经合并到 master 分支。

其实关于 Result 类型相关的实践,很早之前就出现了,这次只是将它添加到 Swift 标准库中。Result 类型是一个 enum 类型,它包含了两个 cases,successfailure ,它们都有不同的关联值类型,对于 failure 的关联值必须遵循 Error
之前对 Result 的实践基本上都是用于处理网络请求结果,所以,也从网络请求开始说起。假设有一个 API 返回一个 Int 值,有如下代码,来处理这样的需求:

1
2
3
4
5
6
7
8
func fetchResult(from urlString: String, completionHandler: @escaping (Result<Int, NetworkError>) -> Void) {
guard let url = URL(string: urlString) else {
completionHandler(.failure(.badURL))
return
}
print("Fetching \(url.absoluteString)...")
completionHandler(.success(5))
}

方法体中,我们处理了请求成功和失败的情况,在通过 completionHandler 来返回异步请求的结果。
可以通过下面的代码来获取我们的请求结果:

1
2
3
4
5
6
fetchResult(from: "https://shanbay.com") { result in
switch result {
case .success(let count): print("\(count) result")
case .failure(let error): debugPrint(error.localizedDescription)
}
}

Result 类型提供了两个特性,可以方便我们获取处理结果。

  • get()
    如果 Result 的结果是 .successget() 的返回值就是处理成功的结果,否则就 throw 出错误。

    1
    2
    3
    4
    5
    fetchResult(from: "https://shanbay.com") { result in
    if let count = try? result.get() {
    print("\(count) result")
    }
    }
  • init(catching body: () throws -> Success)
    初始化方法内接受一个 throing 的闭包,如果闭包的处理结果是成功的情况,则在 success 的 case 里获取,否则 throws 的错误也会放到 failure 的 case 里。

    Result 的转化

    Result 也提供了四个用处很大的方法,分别是 map()flatMap()mapError()flatMapError() 。其中,前两个方法的作用与 Optional 中的同名方法作用类似。
    map() 方法会将 Resultsuccess 结果,通过 closure 转换为你希望得到的结果。如果 Result 的结果是 failure 那么 map() 方法就会忽略你提供的 closure。
    举个例子,有一个获取不大于某个值的随机值方法,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    enum FactorError: Error {
    case belowMinimum
    }

    func generateRandomNumber(maximum: Int) -> Result<Int, FactorError> {
    if maximum < 0 {
    return .failure(.belowMinimum)
    } else {
    let number = Int.random(in: 0...maximum)
    return .success(number)
    }
    }

假设一个这样的场景,我们想要将成功的结果转成字符串打印出来,通过 map() 可以更加优雅的写出来。

1
2
let result = generateRandomNumber(maximum: 5)
let stringNumber = result.map { "Random number \($0)" }

这里的 stringNumber 的类型就是 Result<String, FactorError> ,如果 generateRandomNumber 传入的值为 -11 按照方法中的实现,这里的 result 的值是一个 FactorError. belowMinimumfailure case。使用 map() 时,stringNumber 的类型仍然是 Result<String, FactorError>map() 只转化 success 的结果,而直接返回 failure 的结果。
flatMap() 的作用,也是平铺嵌套结构,例如 Result<Result<String, FactorError>, FactorError> 这样的结构。mapError()flatMapError() 的作用和 map() flatMap() 类似,区别在于,前者直接返回了 success 的情况,而只转化 failure 的情况。

类型动态调用

SE-0216 这个提议对于需要与 JavaScriptPython 等动态语言交互的开发者而言很有用处,这里不做赘述了🤣。

为 enum 的 case 添加未处理提醒

SE-0912 使用 Swift 的 enum 时,必须处理所有 case 的场景。例如下面的代码:

1
2
3
4
5
enum PasswordError: Error {
case short
case obvious
case simple
}

通过 switch 处理这样的枚举时,如果我们避免后期添加新的 case 时报错,可以处理两种情况,而让最后一个情况归类到 default 中。

1
2
3
4
5
6
7
switch password(error: PasswordError) {
switch error {
case .short: print("Password was too short.")
case .obvious: print("Password was too obvious.")
default: print("Password was too simple.")
}
}

如果我们此时添加一种新的 case,也会被归类到 default 中,此时编译没有任何问题,但从逻辑上说不通。所以提议里添加一个 @unknown 关键字,它只能用在 switchdefault 之前。编译器会发出警告,告知你有未处理的 case 。

1
2
3
4
5
6
7
switch password(error: PasswordError) {
switch error {
case .short: print("Password was too short.")
case .obvious: print("Password was too obvious.")
@unknown default: print("Password was too simple.") // warning: switch must be exhaustive
}
}

展开 try? 的结果

SE-0230 通过下面的例子,来看这个提议的修改。

1
2
3
4
5
let x: Int = try? a.returnOptionalInt()
/// Before Swift 5
x is Int??
/// After Swift 5
x is Int?

获取 Int 类型的倍数

SE-0225 这是一个比较有意思的提议,也让我体会到了开源带来的正面效果,集思广益下,让 Swift 变的越来越易用。提议的大意就是判断某个数是否是某个数的倍数,之前我们的做法都是通过 % 操作,判断结果是否为 0。每次都这么写有点重复。于是添加了一个 isMultiple 方法,用来判断一个数是否为另一个数的倍数。大致用法如下:

1
2
3
4
5
6
let testNumber: Int = 4
if testNumber.isMultiple(of: 2) {
// is even
} else {
// is odd
}

计算 Sequence 中符合条件元素的个数

SE-0220 这个提议是我心仪已久的特性了。相比之前每次想要查找 Sequence 中符合条件的元素个数,都需要先通过 filter 过滤之后,再调用 count 的方式,其实,filter 会新创建一个数组,暗地里增加了开销。看函数声明就可以看出 count 的作用了,声明如下:

1
func count(where predicate: (Element) throws -> Bool) rethrows -> Int

Dictionary 添加 compactMapValues()

SE-0218Dictionary 类型添加了一个 compactMapValues() 方法。方法定义如下:

1
public func compactMapValues<T>(_ transform: (Value) throws -> T?) rethrows -> [Key : T]

顾名思义,方法转化 Dictionary 中的 Value 值,返回了处理完成之后的 Dictionary 。也可以用来解包 Optional 并过滤掉 nil ,示例如下:

1
2
3
4
5
6
7
8
 let times = [
"Hudson": "38",
"Clarke": "42",
"Robinson": "35",
"Hartis": "DNF"
]
let result = times.compactMapValues { Int($0) } // 或
let result = times.compactMapValues(Int.init)

JavaScript30 Day 01 【译】使用 LLDB 调试 Swift 代码

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×