์ด์ „ ๊ธ€์—์„œ RunLoop์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์•˜๋‹ค. Timer๋„ ๊ฐ™์ด ์ฒ˜๋ฆฌํ•œ๋‹ค ํ–ˆ์—ˆ๋Š”๋ฐ, ์ด๋ฒˆ์—๋Š” Timer์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋ คํ•œ๋‹ค.

Timer

ํŠน์ • ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ์ด ์ง€๋‚œ ํ›„, Target ๊ฐ์ฒด๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•œ๋‹ค.

๋ฌธ์„œ๋ฅผ ์ฝ์–ด๋ณด๋‹ˆ, Run loop๋Š” Timer์— ๋Œ€ํ•ด ๊ฐ•ํ•œ ์ฐธ์กฐ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, Run loop์— ํƒ€์ด๋จธ๋ฅผ ์ถ”๊ฐ€ํ•œ ํ›„์— ๊ฐ•ํ•œ ์ฐธ์กฐ๋ฅผ ์œ ์ง€ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค๊ณ  ํ•œ๋‹ค. ์ด๊ฒŒ ๋ฌด์Šจ๋ง์ธ๊ฐ€ ์‹ถ์–ด ์ข€ ์ฐพ์•„๋ณด๋‹ˆ, run loop์— timer๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ ๊ทธ ๋’ค๋กœ๋Š” timer ๊ฐ์ฒด๋ฅผ ๋“ค๊ณ  ์žˆ์„ ํ•„์š”๊ฐ€ ์—†๋‹ค๋ผ๋Š” ๋ง์ธ ๊ฒƒ ๊ฐ™๋‹ค. ํ•˜์ง€๋งŒ fire๋ฅผ ์œ„ํ•ด์„œ๋Š” ๋“ค๊ณ  ์žˆ๋Š” ๊ฒƒ์ด ์ข‹์„ ์ˆ˜๋„ ์žˆ๋‹ค.

๊ทธ ๋‹ค์Œ์œผ๋กœ ๋ฌธ์„œ์—์„œ๋Š” Timer๊ฐ€ ์‹ค์‹œ๊ฐ„ ๋งค์ปค๋‹ˆ์ฆ˜์ด ์•„๋‹ˆ๋ผ๊ณ  ํ•œ๋‹ค. ์ด๋Š” Run loop์— ๋Œ€ํ•ด ์•Œ์•„์•ผ ํ•œ๋‹ค. ์ด์ „ ๊ธ€์„ ์ฐธ๊ณ ํ•˜์ž.

์ผ๋‹จ ์ƒ๊ฐํ•ด๋ณด๋ฉด, Timer๋Š” Run loop์•ˆ์—์„œ ๋Œ์•„๊ฐ€๋Š” ๋…€์„์ด๋‹ค. ๊ทธ๋Ÿฐ๋ฐ run loop๋Š” input source์—ญ์‹œ ์ฒ˜๋ฆฌํ•œ๋‹ค. ๋งŒ์•ฝ ๋‚ด๊ฐ€ timer๋ฅผ main thread์—์„œ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•œ ๊ฒฝ์šฐ์ธ๋ฐ, input source๊ฐ€ ๊ต‰์žฅํžˆ ๋งŽ์ด ๋“ค์–ด์˜จ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๋จผ์ € ์Œ“์—ฌ์žˆ๋Š” ์ด ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ ค๊ณ  ํ•  ๊ฒƒ์ด๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๊ทธ์™€์ค‘์— ๊ฐ‘์ž๊ธฐ timer๊ฐ€ fire๋˜์–ด์•ผ ํ•˜๋Š” ์‹œ๊ธฐ๊ฐ€ ์ฐพ์•„์™”๋‹ค. ํ•˜์ง€๋งŒ ์•ž์— ์Œ“์ธ event๊ฐ€ ๋งŽ๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ œ๋กœ ์›ํ•˜๋Š” time interval์ด ์ง€๋‚œ ํ›„์— timer๊ฐ€ fire๋œ๋‹ค. ์ด๋Ÿฌํ•œ ์ ์—์„œ ์‹ค์‹œ๊ฐ„ ๋งค์ปค๋‹ˆ์ฆ˜์ด ์•„๋‹ˆ๋ผ๋Š” ๋ง์„ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ดํ›„ ์˜ˆ์‹œ๋“ค์—์„œ ๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ, ๋ณดํ†ต timer๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์„ ์–ธํ•˜๋Š” ๋ฐฉ์‹์€ ์•”์‹œ์ ์œผ๋กœ main thread์—์„œ ์ฒ˜๋ฆฌ๋œ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ ‡๊ฒŒ real time์ด ์•„๋‹ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์„ ์•„๋Š” ๊ฒƒ์€ ์ค‘์š”ํ•  ์ˆ˜ ์žˆ๋‹ค. (๊ทธ๋ ‡๋‹ค๋ฉด ๋‹ค๋ฅธ run loop์— timer๋ฅผ ๋„ฃ์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ์–˜๊ธฐ์ธ๊ฐ€?: ๊ทธ๋ ‡๋‹ค) ์ด๋ ‡๊ฒŒ timer๊ฐ€ fire๋˜๋Š” ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ์„ Timer Tolerance๋ผ ํ•œ๋‹ค.

Timer ๊ฐ์ฒด๋Š” CFRunLoopTimer์™€ ์—ฐ๊ฒฐ๋œ๋‹ค๊ณ  ํ•œ๋‹ค. ์ •ํ™•ํ•˜์ง€๋Š” ์•Š์œผ๋‚˜, ์ด๋ ‡๊ฒŒ Type casting์„ ํ•˜๋Š”๋ฐ ์žˆ์–ด cost๊ฐ€ ์—†์ด ์ž‘์—…์„ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐœ๋…์„ Toll-Free Bridging์ด๋ผ ๋ถ€๋ฅธ๋‹ค๊ณ  ํ•œ๋‹ค. Objective C์™€ ๊ด€๋ จ๋œ ๊ฐœ๋…์ด๋ผ ์ผ๋‹จ ๋„˜์–ด๊ฐ„๋‹ค.

Comparing Repeating and Nonrepeating Timers

์ผ๋‹จ ํƒ€์ด๋จธ์—์„œ ๊ฐ€์žฅ ํฐ ๋ถ„๋ฅ˜๋Š” ์ด๊ฒƒ์œผ๋กœ ๋งํ•  ์ˆ˜ ์žˆ๋‹ค. ๋ฐ˜๋ณตํ•˜๋Š๋ƒ, ๋ฐ˜๋ณตํ•˜์ง€ ์•Š๋Š๋ƒ.

Repeating Timers

๋ฐ˜๋ณต ํƒ€์ด๋จธ์˜ ๊ฒฝ์šฐ ํŠน์ • ๋ฐ˜๋ณต์‹œ๊ฐ„์„ ๊ธฐ์ค€์œผ๋กœ ์Šค์ผ€์ค„๋ง ๋œ๋‹ค. 5์ดˆ๋งˆ๋‹ค fire๋ฅผ ์›ํ•˜๋Š” ๊ฒฝ์šฐ, ์ผ๋‹จ ์˜ˆ์ •๋œ fire ์ผ์ •์€ 5์ดˆ๊ฐ„๊ฒฉ์œผ๋กœ ์žกํžˆ๊ฒŒ ๋œ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ํ•ด๋‹น run loop์— ๋‹ค๋ฅธ event๋“ค์ด ๋งŽ์ด ์Œ“์—ฌ ์žˆ๋Š” ๊ฒฝ์šฐ ์–ด๋Š์ •๋„๋Š” delay๋œ๋‹ค. ๊ทธ delay ์ˆ˜์ค€์ด time interval๋ณด๋‹ค ๋„˜์–ด๊ฐ„ ๊ฒฝ์šฐ์—๋Š” ๊ทธ ์‹œ๊ฐ„๋™ํ•œ ํ•œ๋ฒˆ๋งŒ fire๋œ๋‹ค.

let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true)
 
@objc func fireTimer() {
    print("Timer fired!")
}

์œ„ ๋ฐฉ์‹์€ @objc๋ฅผ ์‚ฌ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— Target/Action ๋ฐฉ์‹์„ ํ™œ์šฉํ•œ ๊ฒƒ์ด๋‹ค. ๋งŒ์•ฝ objective c ๋ฐฉ์‹์ด ์•„๋‹ˆ๋ผ๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
    print("Timer fired!")
}

ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ return value๋กœ Timer๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ๋ฐ˜๋ณตํ•˜๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•ด๋‘์–ด์•ผ ์ถ”ํ›„ invalidate()๋ฅผ ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฒƒ์ด ์ข‹์„ ์ˆ˜ ์žˆ๋‹ค. ํ˜น์€ closure๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ์‹œ์ ์— invalidateํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜๋‹ค.

Nonrepeating Timers

ํ•œ๋ฒˆ fireํ›„์— ์ž๋™์œผ๋กœ ๋ฌดํšจํ™” ๋œ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ๋‚ด๊ฐ€ ํ•œ๋ฒˆ๋งŒ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋ผ๋ฉด ์ด๋…€์„์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

let timer1 = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: false)
 
let timer2 = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { timer in
    print("Timer fired!")
}

์ด๋Ÿฐ์‹์œผ๋กœ argument์— ์ถ”๊ฐ€ํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๊ตณ์ด ํ•œ๋ฒˆ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์ด๋ ‡๊ฒŒ ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    print("Timer fired!")
}

Ending the timer

์•„๊นŒ๋„ ๋งํ–ˆ์ง€๋งŒ, closure๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ timer ๊ฐ์ฒด๋ฅผ ์ฃผ๊ธฐ ๋•Œ๋ฌธ์—, ๊ทธ ์•ˆ์—์„œ ์ข…๋ฃŒ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค. guard๋ฌธ ๊ฐ™์€ ๊ฑธ๋กœ ealry exit๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜๋„ ์žˆ๊ฒ ๋‹ค.

var runCount = 0
 
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
    print("Timer fired!")
    runCount += 1
 
    if runCount == 3 {
        timer.invalidate()
    }
}

Adding Context

์ด๊ฑด Target/Action ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ์ฆ‰, Objective c runtime์—์„œ ์ฒ˜๋ฆฌํ•  ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜์ด๋‹ค. Timer๊ฐ€ ๋ฐœ๋™๋˜์–ด ํŠน์ • ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰์‹œ๊ธธ ๋•Œ, Timer ๊ฐ์ฒด๊ฐ€ ๊ฐ™์ด ๋„˜์–ด๊ฐ€๊ฒŒ ๋˜๋Š”๋ฐ, ๊ฑฐ๊ธฐ์— ์›ํ•˜๋Š” context ์ •๋ณด๋ฅผ ๋„ฃ์–ด์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

let context = ["user": "@wansook"]
Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), userInfo: context, repeats: true)
 
@objc func fireTimer(timer: Timer) {
    guard let context = timer.userInfo as? [String: String] else { return }
    let user = context["user", default: "Anonymous"]
 
    print("Timer fired by \(user)!")
    runCount += 1
 
    if runCount == 3 {
        timer.invalidate()
    }
}

Timer Tolerance

Timer๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ runloop์•ˆ์—์„œ ๋Œ์•„๊ฐ€๊ธฐ ๋•Œ๋ฌธ์—, event์˜ ์Œ“์—ฌ์žˆ๋Š” ์ •๋„์— ๋”ฐ๋ผ ์‹คํ–‰๋˜๋Š” ์‹œ๊ฐ„์— ์˜ํ–ฅ์„ ๋ฏธ์นœ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— system์ž…์žฅ์—์„œ๋Š” ์ด ์‹œ๊ฐ„์„ ์ž˜์ง€ํ‚ค๊ธฐ ์œ„ํ•ด ์Šค์ผ€์ฅด๋ง์„ ์ž˜ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ด๊ฒŒ cost๊ฐ€ ๋“ค์–ด๊ฐ€๋Š” ์ž‘์—…์ผ ์ˆ˜ ๋ฐ–์— ์—†๋‹ค.

์ด๋Ÿฌํ•œ ์ ์—์„œ Apple์€ ์•ฝ๊ฐ„์˜ ์œ ๋„๋ฆฌ๋ฅผ ๋ถ€์—ฌํ•ด์ค€๋‹ค๋ฉด ์ข€ ๋” ์ „๋ ฅ ์‚ฌ์šฉ๋Ÿ‰์— ๋„์›€์ด ๋ ๊ฑฐ๋ผ๊ณ  ํ•œ๋‹ค. ๊ทธ๊ฒŒ ๋ฐ”๋กœ ์ด ๊ฐœ๋…์ด๋‹ค.

let timer = Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(fireTimer), userInfo: nil, repeats: true)
timer.tolerance = 0.5

5์ดˆ ๋ฐ˜๋ณต timer๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž. ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” tolerance๊ฐ€ 0์ด๊ธฐ ๋•Œ๋ฌธ์— system์€ ์ตœ๋Œ€ํ•œ ์ด ๊ธฐ์ค€์„ ๋งž์ถ”๋ ค๊ณ  ๋…ธ๋ ฅํ•œ๋‹ค. ๊ทธ๋ž˜์„œ ํž˜๋“ค์–ดํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ ๋‚ด๊ฐ€ tolerance๋ฅผ 0.5์ดˆ๋กœ ์ง€์ •ํ•ด์ฃผ๋ฉด ์•ฝ๊ฐ„์˜ ์ˆจํ†ต์ด ํŠธ์ธ๋‹ค. ์ผ๋‹จ์€ ์ตœ๋Œ€ํ•œ tolerance๊ฐ€ 0์— ๋งž์ถ”๋Š” ๊ฑธ ๋…ธ๋ ฅํ•˜๋˜, ์–ด์ฉ” ์ˆ˜ ์—†๋Š” ์‹œ์ ์—์„œ๋Š” tolerance๋ฅผ ๋ณด๊ณ  ์œ ๋„๋ฆฌ์žˆ๊ฒŒ ์ฒ˜๋ฆฌํ•œ๋‹ค.

์—ฌ๊ธฐ์„œ ๋งน์ ์€ ์‹คํ–‰์‹œ๊ฐ„์ด ๋Šฆ์ถฐ์กŒ๋‹ค๊ณ  ํ•ด์„œ ๋‹ค์Œ ์‹คํ–‰์‹œ๊ฐ„์ด ๋Šฆ์ถฐ์ง€๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋ผ๋Š” ์ ์ด๋‹ค. ์œ ๋„๋ฆฌ์žˆ๊ฒŒ ์ฒ˜๋ฆฌํ•œ๋‹ค๋Š” ๊ฒŒ ๋”ฑ ๋งž๋Š” ์„ค๋ช…์ด๋‹ค.

Working with RunLoop

Tolerance๊นŒ์ง€ ์ดํ•ดํ–ˆ๋‹ค๋ฉด ํ•œ๊ฐ€์ง€ ์˜๋ฌธ์ ์ด ๋“ค ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿผ input source๊ฐ€ ๊ณ„์†ํ•ด์„œ ๋“ค์–ด์˜ค๋ฉด timer๊ฐ€ ์ž‘๋™ํ•˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ๋Š”๋ฐ, ์ด๊ฑฐ ๋ฌธ์ œ์•„๋‹Œ๊ฐ€? ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ•ด์น˜๋Š” ๊ฒƒ ์•„๋‹Œ๊ฐ€??

๋งž๋‹ค. ๊ทธ๋Ÿฐ ๊ฒฝ์šฐ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค. ์ผ๋‹จ Timer ๊ฐ์ฒด๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด Main Thread์— ๋„ฃ๋Š” ๋ฐฉ์‹์ด๋‹ค. ๊ทธ๋ ‡๊ฒŒ ๋˜๋‹ˆ ์‚ฌ์šฉ์ž์˜ interaction์ด ๋งŽ์ด ๋“ค์–ด์˜ค๋ฉด timer๊ฐ€ ๋ฐœ๋™๋˜์ง€ ์•Š๋Š”๋‹ค. ๋Œ€ํ‘œ์ ์œผ๋กœ Scroll์ด ์žˆ๊ฒ ๋‹ค. ์†์„ ๋Œ€๊ณ  ์ฒœ์ฒœํžˆ ๊ณ„์†ํ•ด์„œ ์›€์ง์ธ๋‹ค๋ฉด system์€ ๊ณ„์†ํ•ด์„œ event๋ฅผ ๋‚ด๋ณด๋‚ผ ๊ฑฐ๊ณ , ๊ทธ๊ฑธ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณณ์€ main runloop์ด๋‹ค. ๊ทธ๋Ÿฌ๋ฉด timer๊ฐ€ ๋ฐœ๋™๋˜์–ด์•ผ ํ•˜๋Š” ์‹œ์ ์— ๋ฐœ๋™์ด ๋ชป๋˜๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค.

์ด๋Ÿฐ ๊ฒฝ์šฐ ์šฐ๋ฆฌ๊ฐ€ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์€ ๋‚ด๊ฐ€ ์›ํ•˜๋Š” runloop์—์„œ timer๋ฅผ ๋ฐœ๋™์‹œํ‚ค๋Š” ๊ฒƒ์ด๋‹ค.

let timer = Timer(timeInterval: 1.0, target: self, selector: #selector(fireTimer), repeats: true)
RunLoop.current.add(timer, forMode: .common)

์ด ๊ฐœ๋…์€ ๋ฐ˜๋ณตํ•˜์ง€ ์•Š๋Š” timer์—๋„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด ๊ฒฝ์šฐ์—๋Š” DispatchQueue๋ฅผ global queue๋กœ ๋ฐ”๊ฟ”์„œ ์‹คํ–‰์‹œํ‚ค๋Š” ๊ฒƒ์ด ๋ณด๋‹ค ๋‚˜์€ ๋ฐฉ๋ฒ•์ด๋ผ ์ƒ๊ฐ๋œ๋‹ค.

DispatchQueue.global.asyncAfter(deadline: .now() + 1) {
    print("Timer fired!")
}

๋งˆ๋ฌด๋ฆฌ

์ด๋ ‡๊ฒŒ Timer์— ๋Œ€ํ•ด์„œ ์ •๋ฆฌํ•ด๋ณด์•˜๋‹ค. Cheeting note๊ฐ€ ๋  ๋“ฏํ•˜๋‹ค. ๋!

Reference