【swift tip】swift 5.1中不显眼但很重要的知识点

857 阅读3分钟

swift5.1中有很多非常重要的更新,包括ABI的稳定、swiftUI以及围绕swiftUI的一系列新特性<@propertyMapper、@functionBuilder...>等等。

但还有一些小的新特性,可能看上去不怎么起眼、甚至感觉有些没必要。但是使用好了会对我们日常的代码的整洁和性能有比较大的改善。

为有序集合(也就是Array)提供查询差异化的API

在swiftUI的更新机制中,当数据流更改时,只更新发生改变的数据对应的UI,所以精确的计算出两个状态集之间的差异是关键点。为此根据SE-0240提议在标准库的BidirectionalCollection协议中增加了difference方法。(标准库中已支持对Set集合求交集&并集的API,但Set是无序的)

举个例子,在比较流氓的App中<比如我们家的>要抓去用户的通讯录,而且要保持本地和服务端增删数据的同步。比较常用的方式是在每次打开App时,抓取本机的通讯录和本地数据库的通讯录。两个列表做本地比对,然后把增删改的数据同步到后台。

现在有了diff方法,我们可以这样修改代码:

struct ContactUpdateService<Contact: Hashable & Identifiable> -> [Contact] {
    private let database: Database
    private(set) var localContacts: [Contact] = []
    private var updatedContacts: [Contact] = []
    ...

    func update(with newContacts: [Contact]) {
        let diff = newContacts.difference(from: localContacts)

        for change in diff {
            switch change {
            case .insert(let index, let model, let association):
                updatedContacts.append(model,.insert)
            case .remove(let index, let model, let association):
                updatedContacts.append(model,.delete)
            }
        }

        models = newModels
    }
}

新增Identifiable协议

在Swift Evolution 的SE-0261号提议中首次提出Identifiable,之后仅仅是用在swiftUI中,在swift5.1中添加到标准库中<此后我们可以在所有的swift模块中使用该协议了>。通过该协议我们可以扩展现有的API,比如将一个序列转化为一个字典:

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

更多关于Identifiable协议的使用之后单独列举。

可选值在模式匹配中的优化

比如我们有一个音乐类App。在我们的Song实体中有一个当前下载状态的可选属性:

struct Song {
    var downloadState: DownloadState?
}

在现有模式匹配机制中,我们可以直接匹配可选值,但必须在每个case后加问号,代码如下

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

但从swift5.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
    }
}

但是有一个注意点,当我们在模式匹配,只是匹配一个可选值的some和none时,还是需要在some后增加问号的。

使用属性默认值优化结构体的默认构造方法

在struct中,如果我们没有声明自己的构造方法,编译器将为我们自动生成一个默认构造起,这给我们带来了很大的便利。 但是如果我们的结构体属性比较多,这个构造方法的参数列表将会特别长。在以往,我们为了避免这种超长的构造函数,我们经常结合默认值声明自定义构造:

struct CellModel {
	init(cellTitle : String = "",
	         cellDesc : NSAttributedString = NSAttributedString(),
	         isShowArrow: Bool = false,
	         cellType : BaseCellType = .SelectType,
	         cellParams : [ProductNormAttrs] = [],
	         cellSecTitle : String = "",
	         inputPlaceHolderStr : String = "")
	{
		...
	}
}

let model1 = CellModel()
let model2 = CellModel(cellTitle:"Base")
let model2 = CellModel(isShowArrow:true)

swift5.1之后,编译器在为我们生成构造函数时,将自动结合对应属性的默认值,生成优化后的构造方法,如下:

struct Song {
	var id : String?
	var name : String = "Song Name"
	var url : String?

    // auto-generated 
	init(id:String,name:String="Song Name",url:String){
		...
	}
}