SWIFT高级分享 — SWIFT 5.1中的小而重大的改进

46 篇文章 5 订阅
15 篇文章 0 订阅

SWIFT 5.1现在已经正式发布,尽管它只是一个小版本,但它包含了大量的更改和改进-从基本的新特性,比如模块稳定性(它使sdk供应商能够发布预编译的SWIFT框架),到所有的为SwiftUI提供动力的新语法特性,甚至更远的地方。

除了它的新功能外,SWIFT 5.1还包含了一些更小但仍然非常重要的新功能和改进。这种变化一开始看起来很小,甚至没有必要,但最终会对我们编写和构造SWIFT代码的方式产生相当大的影响。本周,让我们来看看其中的五个特性,以及它们在哪些情况下可能有用。

具有默认值的按年初始化器

在SWIFT中,使结构如此吸引人的许多因素之一是它们的自动生成。“成员”初始化器-它使我们能够简单地通过传递与其每个属性对应的值来初始化任何结构(不包含私有存储的属性),如下所示:

struct Message {
    var subject: String
    var body: String
}

let message = Message(subject: "Hello", body: "From Swift")

这些合成的初始化器在SWIFT 5.1中得到了显著改进,因为它们现在考虑到了默认属性值,并自动将这些值转换为默认的初始化器参数。

假设我们想要扩展上面的内容Message结构,支持附件,但我们希望默认值为空数组-同时,我们还希望启用Message初始化,而不必指定body因此,我们还将给该属性一个默认值:

struct Message {
    var subject: String
    var body = ""
    var attachments: [Attachment] = []
}

在SWIFT5.1和更早版本中,我们仍然必须为上述所有属性传递初始化参数,不管它们是否有默认值。但是,在SWIFT5.1中,情况不再是这样-这意味着我们现在可以初始化Message仅通过一个subject,就像这样:

var message = Message(subject: "Hello, world!")

这真的很酷,它使得使用structs比以前更加方便。但也许更酷的是,就像使用标准默认参数时一样,我们仍然可以通过传递一个参数来覆盖任何默认的属性值-这给了我们很大的灵活性:

var message = Message(
    subject: "Hello, world!",
    body: "Swift 5.1 is such a great update!"
)

然而,虽然在应用程序或模块中成员初始化程序非常有用,但它们仍然没有作为模块的公共API的一部分公开-这意味着如果我们构建某种形式的库或框架,我们仍然必须手动定义面向公共的初始化器(目前是这样)。

使用Self引用包围类型

斯威夫特Self关键词(或者类型,真的)以前使我们能够在不知道实际具体类型的上下文中动态引用一种类型-例如,通过在协议扩展中引用协议的实现类型:

extension Numeric {
    func incremented(by value: Self = 1) -> Self {
        return self + value
    }
}

尽管这仍然是可能的,但是Self现在已经扩展到包括具体的类型-例如枚举、结构和类-使我们能够使用。Self作为一种别名,指方法或属性的围封类型,像这样:

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 {
        return Self { $0.capitalized }
    }

    static var removeLetters: Self {
        return Self { $0.filter { !$0.isLetter } }
    }
}

除了引用一个封闭类型本身,我们现在也可以使用Self访问实例方法或属性中的静态成员-在我们希望在一个类型的所有实例中重用相同值的情况下,这是非常有用的,例如cellReuseIdentifier在本例中:

class ListViewController: UITableViewController {
    static let cellReuseIdentifier = "list-cell"

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.register(
            ListTableViewCell.self,
            forCellReuseIdentifier: Self.cellReuseIdentifier
        )
    }
}

再说一次,我们可以简单地打出来ListViewController当访问我们的静态属性时,但是使用Self确实可以提高代码的可读性,并且还可以使我们重新命名视图控制器,而不必更新访问其静态成员的方式。

切换选项

接下来,让我们看一下SWIFT 5.1如何使在选项上执行模式匹配变得更容易,这在打开可选值时确实很方便。例如,假设我们正在开发一个包含Song模型-它有一个downloadState属性,该属性允许我们跟踪歌曲是否已被下载,如果当前正在下载歌曲,依此类推:

struct Song {
    ...
    var downloadState: DownloadState?
}

上面的属性是可选的,原因是我们想要nil表示缺少下载状态,也就是说,如果一首歌根本没有下载。

就像我们看了看“SWIFT中的模式匹配”,SWIFT的高级模式匹配功能使我们能够直接打开可选值-而不必先打开它-但是,在SWIFT 5.1之前,这样做要求我们在每个匹配案例中附加一个问号,如下所示:

func songDownloadStateDidChange(_ song: Song) {
    switch song.downloadState {
    case .downloadInProgress?:
        showProgressIndiator(for: song)
    case .downloadFailed(let error)?:
        showDownloadError(error, for: song)
    case .downloaded?:
        downloadDidFinish(for: song)
    case nil:
        break
    }
}

在SWIFT 5.1中,不再需要那些尾随问号,我们现在可以直接引用每一种情况-就像打开非可选值时一样:

func songDownloadStateDidChange(_ song: Song) {
    switch song.downloadState {
    case .downloadInProgress:
        showProgressIndiator(for: song)
    case .downloadFailed(let error):
        showDownloadError(error, for: song)
    case .downloaded:
        downloadDidFinish(for: song)
    case nil:
        break
    }
}

虽然在进一步减少实现公共模式所需的语法方面,上述变化是值得欢迎的,但它确实带来了轻微的副作用,这可能会破坏某些枚举和开关语句的源代码。

因为SWIFT选项是使用Optional引擎盖下的枚举,我们不再能够在包含以下两个元素的任何枚举上执行上述类型的可选模式匹配。some或none案件-因为这些案件现在将与下列案件发生冲突Optional包含。

然而,可以说,任何包含此类案例的枚举(特别是none),应该使用可选的方法来实现-因为表示潜在缺失的值本质上就是选项符所做的。

这个Identifiable协议

最初是作为SwiftUI初始版本的一部分引入的,Identifiable协议现在已经进入SWIFT标准库,并提供了一种简单和统一的方法来标记任何类型为具有稳定、唯一的标识符。

为了符合这个新协议,我们只需声明一个id属性,该属性可以包含任何Hashable类型-例如String:

struct User: Identifiable {
    typealias ID = String

    var id: ID
    var name: String
}

类似于何时Result作为SWIFT5.0的一部分添加到标准库中,这是现在拥有Identifable任何SWIFT模块都可以使用它在不同的代码基础上共享需求。

例如,使用受限的协议扩展,我们可以添加一个方便的API来转换Sequence它在字典中包含可识别的元素,然后将该扩展作为库的一部分进行销售,而不需要我们自己定义任何协议:

public extension Sequence where Element: Identifiable {
    func keyedByID() -> [Element.ID : Element] {
        var dictionary = [Element.ID : Element]()
        forEach { dictionary[$0.id] = $0 }
        return dictionary
    }
}

上面的api被实现为一个方法,而不是一个计算属性,因为它的时间复杂度是O(N)…有关在方法和计算属性之间选择的更多信息,请参见这篇文章.

然而,当标准库的新Identifiable协议在处理每个值都有一个稳定的标识符的集合时非常有用,它对提高代码的实际类型安全性没有多大作用。

因为这一切Identifiable是否要求我们定义任何可持续的id属性时,它不会保护我们避免不小心混淆标识符-例如在这种情况下,当我们错误地传递User函数的ID,该函数接受Video身份证:

postComment(comment, onVideoWithID: user.id)

所以仍然有很多强有力的用例Identifier类型和更健壮Identifiable协议-比如我们看过的那些“SWIFT中的类型安全标识符”,这就防止了上述错误的发生。不过,现在还是很高兴一些的版本Identifiable协议在标准库中,即使它是比较有限的。

有序收集差

最后,让我们看一个全新的标准库API,它是SWIFT5.1顺序集合差异的一部分。随着我们作为一个社区,越来越接近声明性编程的世界,使用诸如Comit和SwiftUI这样的工具-能够计算两种状态之间的差异变得越来越重要。

毕竟,声明性用户界面开发就是不断地呈现状态的新快照,而Swiftui和新的不同数据源可能会做大部分的繁重工作来实现这一点-能够计算出两种状态之间的差异是非常有用的。

例如,假设我们正在构建一个DatabaseController这样我们就可以轻松地用内存模型数组更新磁盘上的数据库。为了能够确定应该插入还是删除模型,我们现在只需调用新的differenceAPI来计算旧数组和新数组之间的差异,然后迭代该diff中的更改,以执行数据库操作:

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):
            // If the associated index isn't nil, that means
            // that the insert is associated with a removal,
            // and we can treat it as a move.
            if association != nil {
                database.move(model, toIndex: index)
            } else {
                database.insert(model)
            }
        case .remove(_, let model, let association):
            // We'll only process removals if the associated
            // index is nil, since otherwise we will already
            // have handled that operation as a move above.
            if association == nil {
                database.delete(model)
            }
        }
    }
    
    models = newModels
}

现在,Diffing被内置到标准库(以及UIKit和AppKit)中,这是一个奇妙的消息-因为编写一个高效、灵活和健壮的差分算法是非常困难的。

结语

SWIFT5.1不仅是SwiftUI和Combinding的关键推动者,对于任何销售预编译框架的团队来说也是个大新闻,因为SWIFT现在不仅是ABI稳定的,而且是模块稳定的。除此之外,SWIFT5.1还包括许多小的但受欢迎的更改和调整,它们应该适用于几乎所有的代码库-尽管在本文中我们已经看了其中的五个,但在接下来的几周和几个月中,我们将继续深入研究SWIFT5.1的更多方面。

本文中没有包含任何与SwiftUI相关的特性,原因是这些特性在“SWIFT 5.1的特性为SwiftUI的API提供了动力”…静态下标也是如此。“斯威夫特中下标的力量”.

你认为如何?您是否已经将项目迁移到SWIFT5.1,如果是,您最喜欢的新功能是什么?让我知道-连同你可能有的任何问题、评论或反馈-或者通通过加我们的交流群 点击此处进交流群 ,来一起交流或者发布您的问题,意见或反馈。

原文地址:https://www.swiftbysundell.com/articles/5-small-but-significant-improvements-in-swift-5-1/#memberwise-initializers-with-default-values

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值