3/6/2021, 4:47:53 PM
SE-0287提案改进了Swift使用隐式成员表达式的能力。Swift 5.4之后不但可以使用单个 使用,而且可以链起来使用。
例如我们使用隐式成员:
struct ContentViewView1: View {
var body: some View {
Text("You're not my supervisor!")
.foregroundColor(.red)
}
}
我们如果要使用颜色的函数,我们只能用回Color.red.opacity(0.5)
:
struct ContentViewView2: View {
var body: some View {
Text("You're not my supervisor!")
.foregroundColor(Color.red.opacity(0.5))
}
}
这点很违反直觉,Swift 5.4之后就不会报错了“Cannot infer contextual base in reference to member 'red'”。可以更简易使用了:
struct ContentViewView3: View {
var body: some View {
Text("You're not my supervisor!")
.foregroundColor(.red.opacity(0.5))
}
}
SE-0284能够让函数,下标和初始化器能够支持多个可变参数(需要带上参数label)。在此之前只支持一个。
func sumarizeGoals(times: Int..., players: String...) {
let joinedNames = ListFormatter.localizedString(byJoining: players)
let joinedTimes = ListFormatter.localizedString(byJoining: times.map(String.init))
print("\(times.count) goals where scord by \(joinedNames) at the follow minutes:\(joinedTimes)")
}
调用很方便
sumarizeGoals(times: 18, 33, 55, 90, players: "Dani", "jamie", "Roy")
// 4 goals where scord by Dani, jamie, and Roy at the follow minutes:18, 33, 55, and 90
Swift 5.1中非正式的引入了Function Builders。Swift 5.4中SE-0289提案把它升级为了Result Builders。
简单来说,Result Builders最重要功能是可以将我们所需的一系列值一步一步变成一个新值。这个能力也是SwiftUI view创建系统的核心驱动,例如在VStack
有一批子view,Swift会在背后将这些view组合成一个内部的Tupleview
,这样才会被VStack
真正使用。——Result Builder把一系列view变成了一个view。
举例来说,首先我们有个函数返回一个字符串:
func makeSenence1() -> String {
"Why settle for a Duke when you can have a Prince?"
}
print(makeSenence1())
// Why settle for a Duke when you can have a Prince?
这点没问题,但是我们如果需要将多个字符串join在一起呢?我们能像SwifUI那么写么?
很明显会编译错误,Swift 5.4之后,我们可以创建一个result builder来告诉Swift如何去转换。
@resultBuilder
struct SimplestringBuilder {
static func buildBlock(_ parts: String...) -> String {
parts.joined(separator: "\n")
}
}
几句代码,可能不好理解:
@resultBuilder
属性告知SwiftUI,所装饰的类型应该被当作一个result builder。之前类似的想法是@_functionBuilder
。但是有下划线,以为着设计来不为常规使用。buildBlock()
。一般这个函数接受一批数据然后做一些转换。上面例子中,接受0到多个字符串,通过回车符来合并成一个。SimpleStringBuilder
结构体变成了一个result builder。意味着,我们可以在任何字符串join的地方来使用@simpleStringBuilder
。可以直接使用SimpleStringBuilder.buildBlock():
let joined = SimplestringBuilder.buildBlock(
"Why settle for a Duke",
"when you can have",
"a Prince?"
)
print(joined)
// Why settle for a Duke
// when you can have
// a Prince?
当然,我们使用了@resultBuilder
来装饰了我们的SimpleStringBuilder
。我们可以如下使用:
@SimplestringBuilder
func makesentence3() -> String {
"Why settle for a Duke"
"when you can have"
"a Prince?"
}
print(makesentence3())
// Why settle for a Duke
// when you can have
// a Prince?
@resultBuilder
自动将makeSentence()
中的表达式通过SimpleStringBuilder
来转换成一个字符串。
result builder还可以支持if/else, for循环等表达方式。
@resultBuilder
struct ConditionalStringBuilder {
static func buildBlock(_ parts: String...) -> String {
parts.joined(separator: "\n")
}
static func buildEither(first component: String) -> String {
return component
}
static func buildEither(second component: String) -> String {
return component
}
}
可以直接在函数体内使用 if else
@ConditionalStringBuilder
func makesentence4() -> String {
"Why settle for a Duke"
"when you can have"
if Bool.random() {
"a Prince?"
} else {
"a King?"
}
}
// Why settle for a Duke
// when you can have
// a King?
同样,可以使用buildArray()来支持for循环:
@resultBuilder
struct ComplexStringBuilder {
static func buildBlock(_ parts: String...) -> String {
parts.joined(separator: "\n")
}
static func buildEither(first component: String) -> String {
return component
}
static func buildEither(second component: String) -> String {
return component
}
static func buildArray(_ components: [String]) -> String {
components.joined(separator: "\n")
}
}
使用起来很酷
@ComplexStringBuilder
func countDown() -> String {
for i in (0...10).reversed() {
"\(i)..."
}
"Lift off!"
}
print(countDown())
// 10...
// 9...
// 8...
// 7...
// 6...
// 5...
// 4...
// 3...
// 2...
// 1...
// 0...
// Lift off!
还值得一提的是,Swift 5.4中result builder也支持作用在属性上, 它会自动让结构体struct的初始化函数应用result builder。
struct CustomVStack<Content: View>: View {
@ViewBuilder let content:Content
var body: some View {
VStack {
// custom functionality here
content
}
}
}
SE-10069提案,可以让Swift支持嵌套函数中重载。
struct Butter { }
struct Flour { }
struct Sugar { }
func makeCookies() {
func add(item: Butter) {
print("Adding butter…")
}
func add(item: Flour) {
print("Adding flour…")
}
func add(item: Sugar) {
print("Adding sugar…")
}
add(item: Butter())
add(item: Flour())
add(item: Sugar())
}
在Swift 5.4之前,add()
方法只有不再makeCookies()
中才支持重载。
Property wrapper 从Swift 5.1引入,用来装饰属性,复用代码,在Swift 5.4中也支持函数内变量装饰Property wrapper了。
@propertyWrapper struct NonNegative<T: Numeric & Comparable> {
var value: T
var wrappedValue: T {
get { value }
set {
if newValue < 0 {
value = 0
} else {
value = newValue
}
}
}
init(wrappedValue: T) {
if wrappedValue < 0 {
self.value = 0
} else {
self.value = wrappedValue
}
}
}
在Swift 5.4中函数内使用,就可以避免score不会为负数。
func playGame() {
@NonNegative var score = 0
// player was correct
score += 4
// player was correct again
score += 8
// player got one wrong
score -= 15
// player got another one wrong
score -= 16
print(score)
}
SE-0294提案可以支持声明Swift Package声明可执行的target。
这点对想使用@main
属性的情况很有用,因为目前Swift Package包管理会自动寻找main.swift文件,有了这个能力的支持,我们在Package.swift中指定//swift-tools-version:5.4
就可以移除main.swift文件,使用@main
的方式了。
SE-0276提案(Multi-pattern catch clauses),能够让我们在单个catch
块中,捕获多个错误,以此来减少重复代码。
例如,我们有下面的错误码。
enum Temperatureerror: Error {
case tooCold, tooHot
}
如下的业务代码
func getReactortemperature() -> Int {
100
}
func checkreactorOperational() throws -> String {
let temp = getReactortemperature()
if temp < 10 {
throw Temperatureerror.tooCold
} else if temp > 90 {
throw Temperatureerror.tooHot
} else {
return "ok"
}
}
Swift 5.2之后,你可以使用逗号,同时处理tooHot和tooCold。
do {
let result = try checkreactorOperational()
print("Result: \(result)")
} catch Temperatureerror.tooHot, Temperatureerror.tooCold {
print("Shut down the reactor!")
} catch {
print("An unknown error occurred.")
}
SE-0279提案引入了多重尾随闭包,能够让我们更简单的调用有多个闭包参数的函数。
之前我们在SwiftUI中,经常会有如下代码
struct OldContentView: View {
@State private var showOptions = false
var body: some View {
Button(action: {
self.showOptions.toggle()
}) {
Image(systemName: "gear")
}
}
}
现在可以更简化:
struct OldContentView: View {
@State private var showOptions = false
var body: some View {
Button {
self.showOptions.toggle()
} label: {
Image(systemName: "gear")
}
}
}
SE-0266提案可以让我们给非关联枚举和遵循comparable
协议的关联值使用comparable
协议,也就是可以使用<
,>
或类似的操作。
enum Size: Comparable {
case small
case medium
case large
case extraLarge
}
if Size.small < Size.large {
print("That shirt is too small")
}
// That shirt is too small
对于关联值也是支持的:
enum WorldCupResult: Comparable {
case neverWon
case winner(stars: Int)
}
let americanMen = WorldCupResult.neverWon
let americanWomen = WorldCupResult.winner(stars: 4)
let japaneseMen = WorldCupResult.neverWon
let japaneseWomen = WorldCupResult.winner(stars: 1)
let teams = [americanMen, americanWomen, japaneseMen, japaneseWomen]
let sortedByWins = teams.sorted()
print(sortedByWins)
注意这里面的排序默认是根据case的顺序,以及关联值的值大小,上述,winner
的会高于neverWon
,而winner(stars: 4)
大于winner(stars: 1)
。
当然你也可以自定义比较函数:
enum Size2: Comparable {
case medium
case large
case small
case extraLarge
static func < (lhs: Self, rhs: Self) -> Bool {
print(lhs)
print(lhs.hashValue)
print(rhs.hashValue)
return lhs.hashValue > rhs.hashValue
}
}
if Size2.small < Size2.large {
print("That shirt is too small")
}
// That shirt is too small
SE-0269提案允许我们在很多不需要的地方停止使用self
。在此之前,我们需要在任何引用self的地方写上self.
。这样我们就把我们的捕获语义显示化了。然而经常出现的情况是,我们的闭包不会导致引用循环,也就意味着self
是多余的。
例如在之前
struct OldContentView: View {
var body: some View {
List(1..<5) { number in
self.cell(for: number)
}
}
func cell(for number: Int) -> some View {
Text("Cell \(number)")
}
}
因为是在struct中调用self.cell(for:)
,不会导致循环引用。在Swift 5.3中,我们可以这样写:
struct OldContentView: View {
var body: some View {
List(1..<5) { number in
cell(for: number)
}
}
func cell(for number: Int) -> some View {
Text("Cell \(number)")
}
}
SE-0281提案引入了@main
的属性来声明程序的入口。
在此之前,我们需要在main.swfit
文件中,如此启动程序
struct OldApp {
func run() {
print("Running!")
}
}
let app = OldApp()
app.run()
现在我们可以简单如此书写:
@main
struct NewApp {
static func main() {
print("Running!")
}
}
需要注意的是:
@main
了@main
@main
只能用在基类中,它不能被任何子类继承。SE-0280提案允许在泛型类型和extension
的函数中使用where
限制。
例如我们有Stack
的结构体。
struct Stack<Element> {
private var array = [Element]()
mutating func push(_ obj: Element) {
array.append(obj)
}
mutating func pop() -> Element? {
array.popLast()
}
}
Swift 5.3之后,我们可以给stack添加一个sorted()方法,仅仅element遵循Comparable协议。
可以直接在struct中
struct Stack<Element> {
func sorted() -> [Element] where Element: Comparable {
array.sorted()
}
}
也可以在extendion中。
extension Stack {
func sorted() -> [Element] where Element: Comparable {
array.sorted()
}
}
SE-0280提案,允许枚举case 更好的匹配协议protocol。
譬如你会写如下的协议和实现来表达默认值。
protocol Defaultable {
static var defaultValue: Self { get }
}
// make integers have a default value of 0
extension Int: Defaultable {
static var defaultValue: Int { 0 }
}
// make arrays have a default of an empty array
extension Array: Defaultable {
static var defaultValue: Array { [] }
}
// make dictionaries have a default of an empty dictionary
extension Dictionary: Defaultable {
static var defaultValue: Dictionary { [:] }
}
现在同样的事情,也可以用在枚举上。
enum Padding: Defaultable {
case pixels(Int)
case cm(Int)
case defaultValue
}
SE-0268提案为更好的效率,调整了didSet
属性监听的工作方式。简单来说:
oldValue
,那么就会跳过获取oldValue
,叫做“simple” didSet
。didSet
也没有willSet
。则直接在赋值的时候直接改值。当然如果要依赖老方式,可以这么写
didSet {
_ = oldValue
}
SE-0277提案引入了,新的数据类型Float16。这种类型被广泛使用在图形编程和机器学习中。
let first: Float16 = 5
let second: Float32 = 11
let third: Float64 = 7
let fourth: Float80 = 13
这里不再细列,可以参考SE-0271, SE-0278, SE-0272,SE-0273等。
SE-0249提案的实现,Key Path表达式作为函数(Key Path Expressions as Functions)。可以将函数(Root) -> Value
简写为\Root.value
。
例如,我们定义User类型
struct User {
let name: String
let age: Int
let bestFriend: String?
var cnaVote: Bool {
age >= 18
}
}
创建几个实例,并且放入数组
let eric = User(name: "Eric Effiong", age: 18, bestFriend: "Otis Milburn")
let maeve = User(name: "Maeve Wiley", age: 19, bestFriend: nil)
let otis = User(name: "Otis Milburn", age: 17, bestFriend: "Eric Effiong")
let users = [eric, maeve, otis]
Swift 5.2之后,你可以像下面一样使用key path
let userNames = users.map(\.name)
print(userNames)
而在之前稍微啰嗦一点
let oldUserNames = users.map { $0.name }
当然,你也可以如下使用
let kp: (User) -> String? = \User.bestFriend
let bestFriends = users.compactMap(kp)
print(bestFriends)
注意kp需要显示的写明类型,不然默认是KeyPath类型
提案SE-0253为Swift带来可调用的值(Callable values of user-defined nominal types)。具体来说,如果类型实现了名为的callAsFunction()
的方法,其类型的实例就能直接调用。
struct Dice {
var lowerBound: Int
var upperBound: Int
func callAsFunction() -> Int {
(lowerBound...upperBound).randomElement()!
}
}
let d6 = Dice(lowerBound: 1, upperBound: 6)
let roll1 = d6()
print(roll1)
你可以正常定义callAsFunction()
方法,支持throws
和rethrows
,可以使用mutating
。
struct StepCounter {
var steps = 0
mutating func callAsFunction(count: Int) -> Bool {
steps += count
print(steps)
return steps > 10_100
}
}
var steps = StepCounter()
let targetReached = steps(count: 10)
这么甜的语法糖是为了什么?
Swift 5.2 之后,当你使用自定义下标时候,你可以给参数声明默认值了。
struct PoliceForce {
var officers: [String]
subscript(index: Int, default default: String = "Unknown") -> String {
if index >= 0 && index < officers.count {
return officers[index]
} else {
return `default`
}
}
}
//Amy
//Unknown
现在可以像如下自定义值
print(force[-1, default: "The Vulture"])
当然自定义下标,你也可以不给参数家label
struct Multiplier {
subscript(x: Int, y: Int = 1) -> Int {
x * y
}
}
let multiplier = Multiplier()
multiplier[2, 3]
multiplier[4]
let people = ["Arya", "Cersei", "Samwell", "Stannis"]
.lazy
.filter { $0.hasPrefix("S") }
.filter { print($0); return true }
_ = people.count
//Samwell
//Stannis
Swift 5.2 之前使用lazy
,会返回所有,这很违反直觉,因此Swift 5.2 修复了这个问题。
func outer(x: Int) -> (Int, Int) {
func inner(y: Int = x) -> Int {
return y
}
return (inner(), inner(y: 0))
}
Swift 5.2 之后上述可以编译通过。
Swift 5.2之后,改善了,Swift和SwiftUI的错误提示。
Key path expressions as functions, callAsFunction, and more
Swift 5.2 is now available as part of Xcode 11.4. In this article, you’ll get an overview of the changes you’ll see moving to Swift 5.2.
Multiple trailing closures, massive package manager improvements, and more.
Multiple variadic parameters, improved implicit member syntax, result builders, and more!