生まれしもの
- 配列の要素数だけ処理を実行したら自動で止まるTimer(らしきもの)がこの世に生まれた
(このgifでは繰り返しになっているけど、「おわり」でちゃんと止まっているよ)
以前に実装したもの
- 【Swift】iOSで放置型育成ゲームを作るよ(11) ~戦闘画面を作ったぞいの中で「一定間隔で何かを実行して終わった後にも処理を続ける」必要があったよ
- 「一定間隔で何かを実行する」= Timerしか思い浮かばなかったのでこんなイメージで実装したよ
- グローバルにindex変数を保持
- Timerを回して実行したらindexをインクリメント
- 回したい配列分Timerが回ったらTimerを停止する
下のコードはイメージから簡単なコードに落とし込んだものだよ
ViewController.swift
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
var index = 0
let words: [String] = [
"_人人人人人人_\n> ドーン! <\n ̄Y^Y^Y^Y^Y ̄",
"_人人人人人人人人_\n> たーのしー! <\n ̄Y^Y^Y^Y^Y^Y^Y ̄",
"_人人人人人人人人人人人人_\n> 止まるんじゃねぇぞ… <\n ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄",
"_人人人人人人人人人人人人人人人人_\n> ◯◯が得意なフレンズなんだね <\n ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄",
"_人人人人人人人人人人人人_\n> いっぺん死んでみる? <\n ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄"
]
override func viewDidLoad() {
super.viewDidLoad()
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (timer) in
if self.index < self.words.count {
self.label.text = self.words[self.index]
self.index += 1
} else {
timer.invalidate()
self.label.text = "┼ヽ -|r‐、. レ | \nd⌒) ./| _ノ __ノ "
}
}
}
目指した世界
- 上の実装でも動くっちゃあ動くんだけどなんかかっこ悪い感じなのでなんとかできないか考えて見たよ
- indexに変な値が入ってくるとクラッシュするからindexを保持したくない
- なので回したい配列を渡せばindexとか考える必要なく実行してくれて、回し終わったらこちらで続けたい処理を書けると嬉しい
というわけでできた成果物
- CountableTimer.swift
- CoutableTimerDelegateというプロトコルを宣言
- ViewController側で実行したい処理(doSomething)を記述
- 諸々プロパティを用意してinit時にセットする
- startメソッドでtimerを開始
- 配列の回数分処理が回ったらtimerをストップさせて引数のクロージャーを実行する
- CoutableTimerDelegateというプロトコルを宣言
- ViewController.swift
- CountableTimerDelegateを拡張して具体的な処理を書く
- ViewDidLoad時にCountableTimerからインスタンスを生成
- delegateに自身をセットする
- countableTimerのstartメソッドを呼び出して処理をスタート
- 終わった後にしたい処理をクロージャーの中に記述する
CountableTimer.swift
protocol CountableTimerDelegate {
func doSomething(text: Any)
}
final class CountableTimer {
private let interval: TimeInterval
private let target: [Any]
private var count: Int = 0
private let delegate: CountableTimerDelegate
var timer: Timer
init(target: [Any], interval: TimeInterval, delegate: CountableTimerDelegate) {
self.target = target
self.delegate = delegate
self.interval = interval
self.timer = Timer()
}
func start(after: (() -> Void)? = nil) {
timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block: { (timer) in
if self.count < self.target.count {
self.delegate.doSomething(text: self.target[self.count])
self.count += 1
} else {
self.timer.invalidate()
after?()
}
})
}
}
ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
let countableTimer = CountableTimer(target: words, interval: 1.0, delegate: self)
countableTimer.start {
self.label.text = "┼ヽ -|r‐、. レ | \nd⌒) ./| _ノ __ノ "
}
}
extension ViewController: CountableTimerDelegate {
func doSomething(text: Any) {
guard let text = text as? String else {
return
}
label.text = text
}
}
- これを実行するとindexを使っていた時と同じ結果になります
- ViewController側の処理がスッキリしました🤗
結果
_人人人人人人人人人人人人_
> それなりに使いやすい <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
勢いで記事にしたけど
- (結局は処理自体をViewControllerからCountableTimerに移しただけ😇)
さらには
- こんなに煩わしい書き方しなくても👇でいけるんですけどね・・・
ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
for (index, value) in words.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(index) , execute: {
self.label.text = value
})
}
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(words.count)) {
self.label.text = "┼ヽ -|r‐、. レ | \nd⌒) ./| _ノ __ノ "
}
}
最後に
- 有用性があるかは分からないですが、使いやすかったりもっといい感じに書けないかなあって考える時間は楽しかったです(小並感)
- もっとこんな書き方あるぜ! この書き方はここがダメだぜ!ってご指摘大歓迎です! カモン!
エロい詳しい人😊