前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Swift 5.2到5.4新特性整理

Swift 5.2到5.4新特性整理

作者头像
小刀c
发布2022-08-16 15:24:55
2.2K0
发布2022-08-16 15:24:55
举报
文章被收录于专栏:cc log

Swift 5.4

👉Swift 5.4 需要Xcode 12.5以上

改善隐式成员语法

SE-0287提案改进了Swift使用隐式成员表达式的能力。Swift 5.4之后不但可以使用单个 使用,而且可以链起来使用。

例如我们使用隐式成员:

代码语言:javascript
复制
struct ContentViewView1: View {
    var body: some View {
        Text("You're not my supervisor!")
            .foregroundColor(.red)
    }
}

我们如果要使用颜色的函数,我们只能用回Color.red.opacity(0.5)

代码语言:javascript
复制
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'”。可以更简易使用了:

代码语言:javascript
复制
struct ContentViewView3: View {
    var body: some View {
        Text("You're not my supervisor!")
            .foregroundColor(.red.opacity(0.5))
    }
}

函数支持多个可变参数(variadic parameter)

SE-0284能够让函数,下标和初始化器能够支持多个可变参数(需要带上参数label)。在此之前只支持一个。

代码语言:javascript
复制
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)")
}

调用很方便

代码语言:javascript
复制
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

Result Builders

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。

举例来说,首先我们有个函数返回一个字符串:

代码语言:javascript
复制
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如何去转换。

代码语言:javascript
复制
@resultBuilder
struct SimplestringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: "\n")
    }
}

几句代码,可能不好理解:

  • @resultBuilder属性告知SwiftUI,所装饰的类型应该被当作一个result builder。之前类似的想法是@_functionBuilder。但是有下划线,以为着设计来不为常规使用。
  • 每个result builder,都必须至少提供一个静态的buildBlock()。一般这个函数接受一批数据然后做一些转换。上面例子中,接受0到多个字符串,通过回车符来合并成一个。
  • 最后,我们创建的SimpleStringBuilder结构体变成了一个result builder。意味着,我们可以在任何字符串join的地方来使用@simpleStringBuilder

可以直接使用SimpleStringBuilder.buildBlock():

代码语言:javascript
复制
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。我们可以如下使用:

代码语言:javascript
复制
@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循环等表达方式。

代码语言:javascript
复制
@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

代码语言:javascript
复制
@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循环:

代码语言:javascript
复制
@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")
    }
}

使用起来很酷

代码语言:javascript
复制
@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。

代码语言:javascript
复制
struct CustomVStack<Content: View>: View {
    @ViewBuilder let content:Content
    
    var body: some View {
        VStack {
            // custom functionality here
            content
        }
    }
}

嵌套函数支持重载

SE-10069提案,可以让Swift支持嵌套函数中重载。

代码语言:javascript
复制
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支持函数内变量

Property wrapper 从Swift 5.1引入,用来装饰属性,复用代码,在Swift 5.4中也支持函数内变量装饰Property wrapper了。

代码语言:javascript
复制
@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不会为负数。

代码语言:javascript
复制
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)
}

Swift Package支持声明可执行目标

SE-0294提案可以支持声明Swift Package声明可执行的target。

这点对想使用@main属性的情况很有用,因为目前Swift Package包管理会自动寻找main.swift文件,有了这个能力的支持,我们在Package.swift中指定//swift-tools-version:5.4就可以移除main.swift文件,使用@main的方式了。

Swift 5.3

多模式错误捕捉

SE-0276提案(Multi-pattern catch clauses),能够让我们在单个catch块中,捕获多个错误,以此来减少重复代码。

例如,我们有下面的错误码。

代码语言:javascript
复制
enum Temperatureerror: Error {
    case tooCold, tooHot
}

如下的业务代码

代码语言:javascript
复制
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。

代码语言:javascript
复制
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中,经常会有如下代码

代码语言:javascript
复制
struct OldContentView: View {
    @State private var showOptions = false
    
    var body: some View {
        Button(action: {
            self.showOptions.toggle()
        }) {
            Image(systemName: "gear")
        }

    }
}

现在可以更简化:

代码语言:javascript
复制
struct OldContentView: View {
    @State private var showOptions = false
    
    var body: some View {
        Button {
            self.showOptions.toggle()
        } label: {
            Image(systemName: "gear")
        }

    }
}

可比较的枚举

SE-0266提案可以让我们给非关联枚举和遵循comparable协议的关联值使用comparable协议,也就是可以使用<,>或类似的操作。

代码语言:javascript
复制
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

对于关联值也是支持的:

代码语言:javascript
复制
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)

当然你也可以自定义比较函数:

代码语言:javascript
复制
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

self在很多地方不再必须

SE-0269提案允许我们在很多不需要的地方停止使用self。在此之前,我们需要在任何引用self的地方写上self.。这样我们就把我们的捕获语义显示化了。然而经常出现的情况是,我们的闭包不会导致引用循环,也就意味着self是多余的。

例如在之前

代码语言:javascript
复制
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中,我们可以这样写:

代码语言:javascript
复制
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文件中,如此启动程序

代码语言:javascript
复制
struct OldApp {
    func run() {
        print("Running!")
    }
}

let app = OldApp()
app.run()

现在我们可以简单如此书写:

代码语言:javascript
复制
@main
struct NewApp {
    static func main() {
        print("Running!")
    }
}

需要注意的是:

  • 如果你已经使用了main.swift的方式,你就不能再使用@main
  • 只能有一个@main
  • @main只能用在基类中,它不能被任何子类继承。

上下文泛型声明中支持where限制

SE-0280提案允许在泛型类型和extension的函数中使用where限制。

例如我们有Stack的结构体。

代码语言:javascript
复制
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中

代码语言:javascript
复制
struct Stack<Element> {
    func sorted() -> [Element] where Element: Comparable {
        array.sorted()
    }
}

也可以在extendion中。

代码语言:javascript
复制
extension Stack {
    func sorted() -> [Element] where Element: Comparable {
        array.sorted()
    }
}

Enum cases as protocol witnesses

SE-0280提案,允许枚举case 更好的匹配协议protocol。

譬如你会写如下的协议和实现来表达默认值。

代码语言:javascript
复制
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 { [:] }
}

现在同样的事情,也可以用在枚举上。

代码语言:javascript
复制
enum Padding: Defaultable {
    case pixels(Int)
    case cm(Int)
    case defaultValue
}

重新定义didSet

SE-0268提案为更好的效率,调整了didSet属性监听的工作方式。简单来说:

  • 如果didSet中没有引用oldValue,那么就会跳过获取oldValue,叫做“simple” didSet
  • 如果已经是“simple” didSet也没有willSet。则直接在赋值的时候直接改值。

当然如果要依赖老方式,可以这么写

代码语言:javascript
复制
didSet {
    _ = oldValue
}

新的Float16类型

SE-0277提案引入了,新的数据类型Float16。这种类型被广泛使用在图形编程和机器学习中。

代码语言:javascript
复制
let first: Float16 = 5
let second: Float32 = 11
let third: Float64 = 7
let fourth: Float80 = 13

Swift 包管理改进

这里不再细列,可以参考SE-0271SE-0278 SE-0272SE-0273等。

Swift 5.2

Key Path表达式用作函数

SE-0249提案的实现,Key Path表达式作为函数(Key Path Expressions as Functions)。可以将函数(Root) -> Value 简写为\Root.value

例如,我们定义User类型

代码语言:javascript
复制
struct User {
    let name: String
    let age: Int
    let bestFriend: String?
    
    var cnaVote: Bool {
        age >= 18
    }
}

创建几个实例,并且放入数组

代码语言:javascript
复制
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

代码语言:javascript
复制
let userNames = users.map(\.name)
print(userNames)

而在之前稍微啰嗦一点

代码语言:javascript
复制
let oldUserNames = users.map { $0.name }

当然,你也可以如下使用

代码语言:javascript
复制
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()的方法,其类型的实例就能直接调用。

代码语言:javascript
复制
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()方法,支持throwsrethrows,可以使用mutating

代码语言:javascript
复制
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 之后,当你使用自定义下标时候,你可以给参数声明默认值了。

代码语言:javascript
复制
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

现在可以像如下自定义值

代码语言:javascript
复制
print(force[-1, default: "The Vulture"])

当然自定义下标,你也可以不给参数家label

代码语言:javascript
复制
struct Multiplier {
  subscript(x: Int, y: Int = 1) -> Int {
    x * y
  }
}

let multiplier = Multiplier()

multiplier[2, 3]
multiplier[4]

Lazy filter 顺序反转

代码语言:javascript
复制
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 修复了这个问题。

支持使用外作用域值作为默认值

代码语言:javascript
复制
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的错误提示。

Swift 3 到Swift 5.1

参考

What’s new in Swift 5.2

Key path expressions as functions, callAsFunction, and more

What’s New in Swift 5.2

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.

What’s new in Swift 5.3?

Multiple trailing closures, massive package manager improvements, and more.

What’s new in Swift 5.4?

Multiple variadic parameters, improved implicit member syntax, result builders, and more!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-03-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Swift 5.4
    • 改善隐式成员语法
      • 函数支持多个可变参数(variadic parameter)
        • Result Builders
          • 嵌套函数支持重载
            • Property wrapper支持函数内变量
              • Swift Package支持声明可执行目标
              • Swift 5.3
                • 多模式错误捕捉
                  • 多重尾随闭包
                    • 可比较的枚举
                      • self在很多地方不再必须
                        • 基于类型的程序入口
                          • 上下文泛型声明中支持where限制
                            • Enum cases as protocol witnesses
                              • 重新定义didSet
                                • 新的Float16类型
                                  • Swift 包管理改进
                                  • Swift 5.2
                                    • Key Path表达式用作函数
                                      • 可调用的值
                                        • 下标可声明默认参数
                                          • Lazy filter 顺序反转
                                            • 支持使用外作用域值作为默认值
                                              • 更好的错误诊断
                                              • Swift 3 到Swift 5.1
                                              • 参考
                                              领券
                                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档