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

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中获得了显著改进,由于它们如今考虑到了默认属性值,并自动将这些值转换为默认的初始化器参数。swift

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

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的一部分公开-这意味着若是咱们构建某种形式的库或框架,咱们仍然必须手动定义面向公共的初始化器(目前是这样)。bash

使用Self引用包围类型

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

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

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

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,若是是,您最喜欢的新功能是什么?让我知道-连同你可能有的任何问题、评论或反馈-或者统统过加咱们的交流群 点击此处进交流群 ,来一块儿交流或者发布您的问题,意见或反馈。

原文地址:www.swiftbysundell.com/articles/5-…

相关文章
相关标签/搜索