在Swift中撤消历史记录

利用价值语义实现大善

在过去的几周中,有许多博客文章希望向Swift添加动态行为。 Swift已经是一种非常动态的语言:它具有泛型,协议,一等函数,并且标准库中填充了诸如map和filter之类的函数,这些函数动态地获取其操作(不是像KVC那样使用字符串,而是使用函数,这样更安全,更灵活)。 大多数说想要动态行为的人意味着他们特别想反思:他们想在运行时分析和修改程序。

在Swift中,只有非常有限的反射机制,尽管您已经可以在运行时检查并生成代码。 例如,以下是生成可用于NSCoding或JSON序列化的字典的方法:Swift镜像和JSON。

今天,我们将看看在Swift中实现撤消功能。 人们不断提出来进行反思(Objective-C支持)的示例之一是NSUndoManager。 借助struct语义,我们可以以其他方式向应用程序添加撤消支持。 在开始之前,请确保您了解结构在Swift中的工作方式(最重要的是,它们都是唯一的副本)。 显然,本文不会消除在Swift中进行运行时编程的需要,也不会替代NSUndoManager。 这只是一个如何思考的简单例子。

我们将构建一个名为UndoHistory的结构。 需要注意的是,它仅在A为结构体时才有效。 为了保留所有状态的历史记录,我们可以将每个值存储在数组中。 每当我们要更改某些内容时,我们都将其推入阵列,而每当要撤消操作时,便会从阵列中弹出。 我们总是想从初始状态开始,因此我们为此创建一个初始化器:

  struct UndoHistory  { 
private let initialValue:A
私人var历史记录:[A] = []
init(initialValue:A){
self.initialValue =初始值
}
}

例如,如果要向由数组支持的表视图控制器添加撤消支持,则可以创建此结构的值:

  var history = UndoHistory(initialValue:[1、2、3]) 

为了支持对其他结构的撤消操作,我们从一个不同的初始值开始:

  struct Person { 
变量名称:字符串
变量年龄:整数
}
  var personH​​istory = UndoHistory(initialValue:Person(名称:“ Chris”,年龄:31)) 

当然,我们希望有一种获取当前状态并设置当前状态的方法(换句话说:将一个项目添加到我们的历史记录中)。 要获取当前状态,我们只需返回历史记录数组中的最后一项,如果数组为空,则返回初始值。 要设置当前状态,我们只需追加到历史记录数组即可。

 扩展名UndoHistory { 
var currentItem:A {
得到{
返回history.last? 初始值
}
设置{
history.append(newValue)
}
}
}

例如,如果我们想更改人员的年龄,则可以通过新的计算属性轻松地做到这一点:

  personH​​istory.currentItem.age + = 1 
personH​​istory.currentItem.age //打印32

当然,没有撤消方法,代码是不完整的。 这就像从数组中删除最后一项一样简单。 根据您的喜好,您还可以在撤消堆栈为空时将其引发,但是我选择不这样做。

 扩展名UndoHistory { 
变异func undo(){
守卫!history.isEmpty else {return}
history.removeLast()
}
}

使用起来很简单:

  personH​​istory.undo() 
personH​​istory.currentItem.age //再次打印31

当然,我们的UndoHistory不仅适用于简单的Personstructs。 例如,如果我们要创建一个由Array支持的表视图控制器,则可以使用currentItem属性将数组取出:

 最后一个类MyTableViewController :UITableViewController { 
var数据:UndoHistory

init(value:[Item]){
数据= UndoHistory(initialValue:值)
super.init(style:.Plain)
}

覆盖func tableView(tableView:UITableView,numberOfRowsInSection部分:Int)-> Int {
返回data.currentItem.count
}

覆盖func tableView(tableView:UITableView,cellForRowAtIndexPath indexPath:NSIndexPath)-> UITableViewCell {
让单元格= tableView.dequeueReusableCellWithIdentifier(“ Identifier”,forIndexPath:indexPath)
让item = data.currentItem [indexPath.row]
//用`item`配置`cell`
返回单元
}
 覆盖func tableView(tableView:UITableView,commitEditingStyle editorStyle:UITableViewCellEditingStyle,forRowAtIndexPath indexPath:NSIndexPath){ 
卫队editingStyle ==。删除其他{返回}
data.currentItem.removeAtIndex(indexPath.row)
}
}

结构语义确实很酷的另一件事:我们免费获得观察。 例如,我们可以更改数据的定义:

  var数据:UndoHistory  { 
didSet {
tableView.reloadData()
}
}

即使我们更改了数组内部的某些内容(例如,egdata.currentItem [17] .name =“ John”),didSet也将被触发。 当然,我们可能想做一些比reloadData聪明的事情。 例如,我们可以使用Changeset库来计算差异并具有插入/删除/移动动画2。

显然,这种方法也有其缺点。 例如,它保留状态的完整历史,而不是差异。 它仅适用于结构(准确地说:仅适用于具有值语义的结构)。 就是说,您不必阅读运行时编程指南,只需要对结构和泛型有很好的了解即可提出此解决方案。