Swift 5.1中 几个小却重要的改进

Swift 5.1已正式发布一段时间了,尽管它只是一个次要小版本,却包含了大量的更改和改进。本周,让我们来看看其中的四个特性,以及它们在哪些情况下可能有用。

具有默认值的成员初始化器

在Swift中,结构体如此吸引人的许多因素之一是他们拥有自动生成的成员变量构造器。成员变量构造函数,使我们能够简单地通过传递与其每个属性对应的值来初始化结构体(不包含私有存储的属性),如下所示:

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

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

自动生成的构造函数在Swift 5.1中得到了显著改进,因为它们现在考虑了默认属性值,并自动将这些值转换为默认的构造函数参数。

假设我们扩展上面的Message结构体,添加一个attachmentsbody属性,attachments有一个空数组的默认值,body属性有一个""空字符串默认值:

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

Swift5.0和更早版本中,想要实例化这个结构体,我们必须为上述所有属性传递初始化参数,不管它们是否有默认值。但是,在Swift5.1中,我们不需要这么做了,我们现在可以初始化Message仅传入一个subject,就像这样:

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

这真的很酷,我们初始化结构体比以前更加方便了。 但是,也许更酷的是,就像使用标准默认参数时一样,我们仍然可以通过为其传递参数来覆盖任何默认属性,这提供了很大的灵活性:

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

但是,尽管成员初始化方法在AppModule中非常有用,但它们仍未作为Module公共API的一部分公开,这意味着,如果我们创建frameork或者pod库时,仍然需要我们手动创建构造函数并添加public

使用Self引用封闭类型

以前Swift的Self关键字(实际是类型)使我们能够在不知道具体类型的上下文中动态引用类型,例如,在协议扩展中引用协议的实现类型:

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

现在它仍然可行,并且Swift5.1扩展了Self的使用范围,使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来访问静态的方法和属性,这在我们要在类型的所有实例之间重用相同值的情况下非常有用。 在下面例子中,使用Self访问cellReuseIdentifier

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

    override func viewDidLoad() {
        super.viewDidLoad()

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

当然,我们可以在访问静态属性时简单地在上面使用ListViewController,但是使用Self确实可以提高代码的可读性,并且在我们重命名视图控制器时,不必更新访问其静态成员的方式。。

switch可选值

接下来,让我们看一下Swift 5.1如何让可选对象的执行模式匹配变得更加容易的。 举例来说,假设我们正在开发一个包含Song模型的音乐类App,Song具有可选的downloadState属性:

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

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

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 5.1的一部分引入的全新标准库API,有序集合差异(ordered collection diffing)。 随着Combine和SwiftUI之类的工具越来越接近声明式编程的世界,能够计算两种状态之间的差异变得越来越重要。毕竟,声明式UI开发就是关于不断呈现状态的新快照。

例如,假设我们正在构建一个DatabaseController,它使我们能够通过内存中的Model轻松地更新磁盘上的数据库。 为了弄清楚是应该插入还是删除Model,我们现在可以简单地调用新的difference(from:)API来计算旧数组与新数组之间的差异,然后依次遍历该差异中的更改执行我们的数据库操作:

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
    }
}

但是,上述实现并未考虑已移动的Model,因为默认情况下,移动将被视为单独的插入删除。 为了解决这个问题,我们在计算差异时也要调用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
}

Swift 5.1将difference(from: )内置到标准库(以及UIKit和AppKit中)真是太了不起了,因为编写高效,灵活和健壮的差异算法非常困难。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,622评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,716评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,746评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,991评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,706评论 6 413
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,036评论 1 329
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,029评论 3 450
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,203评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,725评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,451评论 3 361
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,677评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,161评论 5 365
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,857评论 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,266评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,606评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,407评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,643评论 2 380

推荐阅读更多精彩内容