- 原文リンク : Undo History in Swift
- 原文著者 : chriseidhof
- 訳文出自 : 掘金翻訳計画
- 訳者 : Zheaoli
- 校正者: xcc3641, Jaeger
最近、多くのブログがSwiftに追加したい動的特性についての記事を発表しています。実際、Swiftはかなり多くの動的特性を持つ言語になっています:それはジェネリクス、プロトコル、ファーストクラス関数(訳者注 1:ファーストクラス関数は、関数がクラスのように引数として渡されることを指します)、およびmapやfilterなどの多くの動的操作を含む標準ライブラリを持っています(これは、より安全で柔軟な関数を使用して KVC の代わりに文字列を使用できることを意味します)(訳者注 2:KVC は Key-Value-Coding の略で、オブジェクトのプロパティに間接的にアクセスするためのメカニズムを提供する非公式なプロトコルです)。ほとんどの人にとって、特にリフレクションという特性を紹介したいと思っています。これは、プログラムの実行中に観察および変更できることを意味します。
Swiftでは、リフレクションメカニズムには多くの制限がありますが、それでもコードが実行されているときに動的に何かを生成および挿入することができます。例えば、ここではNSCodingまたは JSON のために動的に辞書を生成する方法の例です。
今日は、Swiftで撤回機能を実装する方法を一緒に見ていきましょう。1 つの方法は、Objective-Cに基づくリフレクションメカニズムが提供するNSUndoManagerを利用することです。structを利用することで、私たちのアプリで撤回機能を実装するためのさまざまな方法を利用できます。チュートリアルを始める前に、必ず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]
// `cell`を`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のもう 1 つの非常に素晴らしい特性は、リスナー・パターンを自由に使用できることです。例えば、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履歴には重複した記録が存在することに気づくでしょう。