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){
...
}
}