- 原文連結 : Undo History in Swift
- 原文作者 : chriseidhof
- 譯文出自 : 掘金翻譯計劃
- 譯者 : Zheaoli
- 校對者: xcc3641, Jaeger
在過去的一段時間裡,有很多的Blog推出了關於他們想在Swift中所新增的動態特性的文章。事實上Swift 已經成為了一門具有相當多動態特性的語言:它擁有泛型,協議, 頭等函式(譯者注1:first-class function指函式可以向類一樣作為引數傳遞),和包含很多可以的動態操作的函式的標準庫,比如map和filter等(這意味著我們可以利用更安全更靈活的函式來代替 KVC 來使用 字串)(譯者注2:KVC指Key-Value-Coding一個非正式的 Protocol,提供一種機制來間接訪問物件的屬性)。對於大多數人而言,特別希望介紹反射這一特性,這意味著他們可以在程式執行時進行觀察和修改。
在Swift中,反射機制受到很多的限制,但是你仍然你可以在程式碼執行的時候動態的生成和插入一些東西。 比如這裡是怎樣為NSCoding或者是JSON動態生成字典的例項。
今天在這裡,我們將一起看一下在Swift中怎樣去實現撤銷功能。 其中一種方法是通過利用Objective-C中基於的反射機制所提供的NSUndoManager。通過利用struct,我們可以利用不同的方式在我們的APP中實現撤銷這一功能。 在教程開始之前,請務必確保你自己已經理解了Swift中struct的工作機制(最重要的是理解他們都是獨立的拷貝)。
首先要宣告的一點是,這篇文章並不是想告訴大家我們不需要對runtime進行操作,或者我們提供的是一種NSUndoManager的替代品。這篇文章只是告訴了大家一種不同的思考方式而已。
我們首先建立一個叫做UndoHistory的struct。 通常而言,建立UndoHistory時會伴隨一個警告,提示只有當A是一個struct的時才會生效。為了儲存所有狀態資訊,我們需要將其存放入一個陣列之中。當我們修改了什麼時,我們只需要將其push進陣列中,當我們希望進行撤回時,我們將其從陣列中pop出去。我們通常希望有一個初試狀態,所以我們需要建立一個初始化方法:
struct UndoHistory<A> {
private let initialValue: A
private var history: [A] = []
init(initialValue: A) {
self.initialValue = initialValue
}
}
複製程式碼
舉個例子,如果我們想在一個tableViewController中通過陣列的方式提供撤銷操作,我們可以建立這樣一個struct:
var history = UndoHistory(initialValue: [1, 2, 3])
複製程式碼
對於不同情境下的撤銷操作,我們可以建立不同的struct來實現:
struct Person {
var name: String
var age: Int
}
複製程式碼
var personHistory = UndoHistory(initialValue: Person(name: "Chris", age: 31))
複製程式碼
當然,我們希望獲得當前的狀態,同時設定當前狀態。(換句話說:我們希望實時地操作我們的歷史記錄)。我們可以從history陣列中的最後一項值來獲取我們的狀態,同時如果陣列為空的話,我們便返回我們的初始值。 我們可以通過將當前狀態新增至history陣列來改變我們的操作狀態。
extension UndoHistory {
var currentItem: A {
get {
return history.last ?? initialValue
}
set {
history.append(newValue)
}
}
}
複製程式碼
比如,如果我們想修改個人年齡(譯者注3:指前面作者編寫的Person結構體中的age屬性), 我們可以通過重新計算屬性來很輕鬆的做到這一點:
personHistory.currentItem.age += 1
personHistory.currentItem.age // Prints 32
複製程式碼
當然,undo 方法的編寫並未完成。對於從陣列中移出最後一個元素來講是非常簡單的。 根據你自己的宣告,你可以在陣列為空的時候丟擲一個異常,不過,我沒有選擇這樣一種做法。
extension UndoHistory {
mutating func undo() {
guard !history.isEmpty else { return }
history.removeLast()
}
}
複製程式碼
很簡單的使用它(譯者注4:這裡指作者前面所編寫的undo相關程式碼)
personHistory.undo()
personHistory.currentItem.age // Prints 31 again
複製程式碼
當然,我們到現在的UndoHistory操作只是基於一個很簡單的Person類。比如,如果我們想利用Array來實現一個tableviewcontroller的undo操作,我們可以利用屬性來獲取從陣列中得到的元素:
final class MyTableViewController<item>: UITableViewController {
var data: UndoHistory<[item]>
init(value: [Item]) {
data = UndoHistory(initialValue: value)
super.init(style: .Plain)
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.currentItem.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Identifier", forIndexPath: indexPath)
let item = data.currentItem[indexPath.row]
// configure `cell` with `item`
return cell
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
guard editingStyle == .Delete else { return }
data.currentItem.removeAtIndex(indexPath.row)
}
}
複製程式碼
在struct中另一個非常爽的特性是:我們可以自由的使用監聽者模式。 比如,我們可以修改data的值:
var data: UndoHistory<[item]> {
didSet {
tableView.reloadData()
}
}
複製程式碼
我們即使是修改陣列內很深的值(比如:data.currentItem[17].name = “John”),我們通過didSet也能很方便地定位到修改的地方。當然,我們可能希望做一些例如reloadData這樣方便的事情。比如, 我們可以利用Changeset 庫來計算變化,然後來根據插入/刪除/移動/等不同的操作來新增動畫。
很明顯的是, 這種方法有著它自身的缺點。例如,它儲存了整個狀態的歷史操作,不是每次狀態變化之間的不同點。 這種方法只使用了struct來實現undo操作 (更為準確的講:是隻使用了struct中值的一些特性)。這意味著,你並不需要去閱讀 runtime程式設計指導這本書, 你只需要對struct和generics(譯者注5:generics指泛型)有足夠的瞭解。
- 為data.currentItem提供了一個可計算的屬性 items 來進行獲取和設定操作,是一個不錯的想法。這使得data-source和delegate等方法的實現變得更為容易。
- 如果你想更進一步優化,這裡有一些非常有意思的想法:新增恢復功能,或者是編輯功能。你可以在tableView中去實現, 如果你真的很天真的按照這個去做了,那麼你會發現在你的undo歷史中會存在重複記錄。
關於翻牆(硬廣)
這只是一個友情推薦,我現在使用的是一起艾斯,ipv4,ipv6線路全覆蓋,唔,站長人也很好,基本你的合理需求,站長都能滿足。建議可以試試。