日付時刻を間接参照で利用する方法に付いて、長く書いてしまいました。機能ごとに分けて書いたので、考え方は分かりやすくなりましたが、最終的に全部入りはどうなるの?という疑問が生じたかもしれません。整理した最終状態を示す意味で、まとめ編を書きます。
複数の機能を組み合わせる場合には、それらの関係を整理する必要があります。進行倍率変更の機能では、開始時刻をもっています。それに時間シフト機能を加えたとき、どのような仕様にすべきなのでしょうか。
一番大事なのは、時間シフト機能をどう加えるかです。時間シフト量も、進行倍率の影響を受けるべきなのでしょうか。その判断では、アプリを対話式で使う場合の見え方を考慮します。もしシフト量が倍率の影響を受けたら、わざわざ逆算してシフト量を指定しなければなりません。シフト量はそのままの値が反映されるべきでしょう。
時間進行倍率の開始時刻と、時間シフト量の関係はどうでしょうか。単純に加算する方法が直感的に分かりやすく、それで問題ないでしょう。つまり結論は、進行倍率変更の機能で求めた時刻に、時間シフト量を加えるということです。より詳しい計算方法は、開始時刻に、進行倍率を加味した経過時間を加算し、最後に時間シフト量を加算するという形になります。
さらに使いやすくするためには、もう1つの機能が必要でしょう。アプリが動作する日付を、任意の日付に変更する機能です。自由な日付を設定できると、大晦日から元旦への移行処理をテストするとか、目的の日付でのテストが好きなときに可能となります。
時間シフト機能でも実現可能ですが、実行時の日付との差を計算していたら、非常に面倒です。任意の日付と時刻を指定して使えるほうが、明らかに便利です。機能的には、2種類目のシフト機能と言えるでしょう。
実現方法ですが、今までの全機能の最後に、新しいシフト量を加算する形が一番美しいでしょう。時間のシフト量ですから、値の種類はNSDateのtimeIntervalとなります。設定したい任意の日付と、何かの日付を比較して、その差の分だけtimeInterval値を求めて、変数の保存しておきます。そして、現在の日付時刻の計算の際に、保存しておいたtimeInterval値を加算するというわけです。
何の値との差を利用したら良いでしょうか。今まで作った機能の中では、時間進行倍率の開始時刻が一番適してそうです。別な値を新たに用意する方法もありますが、全体の構造がより複雑化します。もう1つのシフト機能も、この開始時刻を使っているので、同じにしたほうが、全体の動作を理解しやすいでしょう。ということで、時間進行倍率の開始時刻との差を利用し、timeInterval値として保存する形とします。
機能が出揃ったので、整理しましょう。時間進行倍率の変更機能では、開始時刻を指定すると、そのときの現実の時刻が設定されます。続けて倍率係数を設定すれば、開始時刻からの時間経過が、倍率で乗算され、その結果が計算後の時刻となります。
続いて、時間シフト機能に設定されたtimeInterval値が、計算後の時刻に加算されます。最後に、任意の時刻との差であるtimeInterval値が、さらに加算されて最終結果の時刻となります。
これらの処理を、1つの関数にまとめましょう。今まで作ったswiftソースコードを組み合わせて、次のようにします。
// 変数の準備
var timeIntervalShift : Double = 0.0
var timeIntervalWarp : Double = 0.0
var dateStart : NSDate = NSDate()
var timeRetio : Double = 1.0
// 3つの日付時間機能を統合した関数
func getDateNow4TestF(rDateNow:NSDate) -> NSDate {
// まず先に、進行倍率を変えたときの時刻を求める
let realTimeInterval : NSTimeInterval = dateStart.timeIntervalSinceNow
let vartualTimeInterval : NSTimeInterval = realTimeInterval * timeRetio * -1.0
let vartualDate = dateStart.dateByAddingTimeInterval(vartualTimeInterval)
// 最後に、2種類のシフト量を加算
let dateShifted = vartualDate.dateByAddingTimeInterval(timeIntervalShift)
let dateWarped = dateShifted.dateByAddingTimeInterval(timeIntervalWarp)
return dateWarped
}
// これらは、DateControllerクラスの中に作る
2種類のシフト機能の名前ですが、シフト量を直接指定するほうを「Shift」と、任意の日付時刻を指定するほうを「Warp」としました。これを呼び出す形で、「getDateNowF」メソッドを作ります。
// 現在の時刻を得る
func getDateNowF() -> NSDate {
let dateNow = NSDate()
//return dateNow
return getDateNow4TestF(dateNow)
}
別な関数に分けたのは、このメソッドを分かりやすくするためです。また、追加した機能を簡単に消せるようにと、関数を呼ばないreturnもコメントとして残しました。テスト後には、これらの機能を使えなくするために、コメント用の「//」を削除し、関数呼び出し側をコメント化すれば良いでしょう。
前回までの機能のメソッドも、一応挙げておきましょう。
// 時間のシフト量を設定
func setTimeIntervalF(rTimeInterval:Double) {
timeIntervalShift = rTimeInterval
}
// 時間進行の開始時刻を、現在の時刻に設定
func setStartDateNowF() {
dateStart = NSDate()
}
// 時間の進行倍率を設定
func setTimeRatioF(rRatio:Double) {
timeRetio = rRatio
}
なお、メソッドの名前が、前回までと少し変わっています。
新しく追加した、日付の任意設定機能にも、メソッドが必要です。1つのメソッドとして作るよりも、日付と時刻からNSDateの値を作るメソッドと、その値を使ってtimeInterval値を設定するメソッドに分けたほうが、幅広く使えるでしょう。
// 時間進行倍率の開始時刻を、任意の時刻に設定
func setStartDateF(rDate:NSDate) {
timeIntervalWarp = rDate.timeIntervalSinceDate(dateStart)
}
// 任意の日付でNSDate値を作る
func createDateF(rYear:Int, _ rMonth:Int, _ rDay:Int, _ rHour:Int, _ rMin:Int, _ rSec:Int) -> NSDate {
var iCalendar : NSCalendar = NSCalendar.currentCalendar()
var iCompo = NSDateComponents()
iCompo.year = rYear
iCompo.month = rMonth
iCompo.day = rDay
iCompo.hour = rHour
iCompo.minute = rMin
iCompo.second = rSec
let iDate : NSDate = iCalendar.dateFromComponents(iCompo)!
return iDate
}
これで機能的には十分ですが、あと1つだけメソッドを追加します。全部の設定をリセットして、何の機能も動いてない状態に戻すメソッドです。これがあると、テストのときに便利です。
// すべての機能をリセットする
func resetAllDateF() {
timeIntervalShift = 0.0
timeIntervalWarp = 0.0
setStartDateNowF()
timeRetio = 1.0
}
ここでも「= NSDate()」を増やさないようにと考え、「setStartDateNowF」メソッドを使いました。
以上が、ここまで説明した機能の総合版です。長く説明した割には、短いソースコードになっていますね。
新しい機能だけは、使い方を紹介しましょう。
// クラスを使う
let zDate = DateController() // 適切なタイミングでインスタンス生成
...
zDate.setStartDateNowF() // 念のため、開始時刻を現在に設定
iDate = zDate.createDateF(2014, 12, 31, 23, 50, 0)
zDate.setStartDateF(iDate) // 任意の日付時刻にシフト
...
labelDate.text = zDate.getDateStrNowF() // 必要な箇所で使う
...
zDate.setStartDateNowF() // 念のため、開始時刻を現在に設定
iDate = zDate.createDateF(2015, 1, 1, 0, 10, 0)
zDate.setStartDateF(iDate) // 任意の日付時刻にシフト
...
labelDate.text = zDate.getDateStrNowF() // 必要な箇所で使う
...
zDate.resetAllDateF() // 必要に応じて全部リセット
使うときは、1点だけ注意が必要です。時間進行倍率の開始時刻と関係しているため、進行倍率を使わない場合でも、開始時刻がいつの設定になっているのか意識する必要があります(何もしていなければ、インスタンスを生成した時刻です)。念のために、開始時刻を現在の時刻に設定してから、任意の日付時刻に変更すると良いでしょう。
今回のように、複数の機能を組み合わせて使えるように仕上げた場合は、互いの影響を考慮しながら使う必要があります。どのように計算して日付時刻が決まるのか、きっちりと頭に入れておくことが求められますね。間違えずに使えれば、非常に便利な機能です。
日付時刻の機能拡張に、かなりの回数を費やしてしまいました。一度作っておくと全部のアプリで使えますし、作るまでの考え方のほうが重要なので、これぐらい長く説明しても意味があると思います。日付時刻以外の機能でも、間接参照をどんどんと利用して、変更に対して柔軟で、しかもテストしやすいアプリを作りましょう。
(使用開発ツール:Xcode 6.0.1, SDK iOS 8.0)